<!DOCTYPE HTML>
<html lang="zh-CN">


<head>
    <meta charset="utf-8">
    <meta name="keywords" content="kubernetes, 博客 blog">
    <meta name="description" content="熊猫小二的博客  xmxe&#39;s blog">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="format-detection" content="telephone=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
    <!-- 为了引用qq空间图床文件 -->
    <meta name="referrer" content="no-referrer">
    <!-- Global site tag (gtag.js) - Google Analytics -->


    <title>kubernetes | 熊猫小二</title>
    <link rel="icon" type="image/x-icon, image/vnd.microsoft.icon" href="/blog/favicon.ico">
    <link rel="stylesheet" type="text/css" href="/blog/libs/awesome/css/all.css">
    <link rel="stylesheet" type="text/css" href="/blog/libs/materialize/materialize.min.css">
    <link rel="stylesheet" type="text/css" href="/blog/libs/aos/aos.css">
    <link rel="stylesheet" type="text/css" href="/blog/libs/animate/animate.min.css">
    <link rel="stylesheet" type="text/css" href="/blog/libs/lightGallery/css/lightgallery.min.css">
    <link rel="stylesheet" type="text/css" href="/blog/css/matery.css">
    <link rel="stylesheet" type="text/css" href="/blog/css/my.css">
    <link rel="stylesheet" type="text/css" href="/blog/css/loading.css">

    <script src="/blog/libs/jquery/jquery.min.js"></script>

<meta name="generator" content="Hexo 6.3.0"></head>



   
<style>
    body{
       background-image: url(/blog/medias/cover.jpg);
       background-repeat:no-repeat;
       background-size:cover;
       background-attachment:fixed;
    }
</style>



<body>
    
  <div id="loading-box">
    <div class="loading-left-bg"></div>
    <div class="loading-right-bg"></div>
    <div class="spinner-box">
      <div class="configure-border-1">
        <div class="configure-core"></div>
      </div>
      <div class="configure-border-2">
        <div class="configure-core"></div>
      </div>
      <div class="loading-word">加载中...</div>
    </div>
  </div>
  <!-- 页面加载动画 -->
  <script>
    $(document).ready(function () {
      // document.body.style.overflow = 'auto';
      document.getElementById('loading-box').classList.add("loaded")
    })
  </script>

    <header class="navbar-fixed">
    <nav id="headNav" class="bg-color nav-transparent">
        <div id="navContainer" class="nav-wrapper container">
            <div class="brand-logo">
                <a href="/blog/" class="waves-effect waves-light">
                    
                        <img src="/blog/medias/logo.png" class="logo-img" alt="LOGO">
                    
                    <span class="logo-span">熊猫小二</span>
                </a>
            </div>
            

<a href="#" data-target="mobile-nav" class="sidenav-trigger button-collapse"><i class="fas fa-bars"></i></a>
<ul class="right nav-menu">
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/blog/" class="waves-effect waves-light">
      
      <i class="fas fa-home" style="zoom: 0.6;"></i>
      
      <span>首页</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="#" class="waves-effect waves-light">

      
      <i class="fas fa-book" style="zoom: 0.6;"></i>
      
      <span>归档</span>
      <i class="fas fa-chevron-down" aria-hidden="true" style="zoom: 0.6;"></i>
    </a>
    <ul class="sub-nav menus_item_child ">
      
      <li>
        <a href="/blog/archives">
          
          <i class="fas fa-archive" style="margin-top: -20px; zoom: 0.6;"></i>
          
          <span>归档</span>
        </a>
      </li>
      
      <li>
        <a href="/blog/tags">
          
          <i class="fas fa-tags" style="margin-top: -20px; zoom: 0.6;"></i>
          
          <span>标签</span>
        </a>
      </li>
      
      <li>
        <a href="/blog/categories">
          
          <i class="fas fa-bookmark" style="margin-top: -20px; zoom: 0.6;"></i>
          
          <span>分类</span>
        </a>
      </li>
      
    </ul>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="/blog/friends" class="waves-effect waves-light">
      
      <i class="fas fa-address-book" style="zoom: 0.6;"></i>
      
      <span>友链</span>
    </a>
    
  </li>
  
  <li class="hide-on-med-and-down nav-item">
    
    <a href="#" class="waves-effect waves-light">

      
      <i class="fas fa-user-circle" style="zoom: 0.6;"></i>
      
      <span>关于</span>
      <i class="fas fa-chevron-down" aria-hidden="true" style="zoom: 0.6;"></i>
    </a>
    <ul class="sub-nav menus_item_child ">
      
      <li>
        <a href="/blog/about">
          
          <i class="fas fa-star-of-david" style="margin-top: -20px; zoom: 0.6;"></i>
          
          <span>主页</span>
        </a>
      </li>
      
      <li>
        <a href="/blog/gallery">
          
          <i class="fas fa-images" style="margin-top: -20px; zoom: 0.6;"></i>
          
          <span>相册</span>
        </a>
      </li>
      
    </ul>
    
  </li>
  
  <li>
    <a href="#searchModal" class="modal-trigger waves-effect waves-light">
      <i id="searchIcon" class="fas fa-search" title="搜索" style="zoom: 0.85;"></i>
    </a>
  </li>
  
    <li>
      <a class="waves-effect waves-light" onclick="switchNightMode()">
        <i id="sum-moon-icon" class="fas fa-sun" style="zoom:0.65;" title="切换主题"></i>
      </a>
    </li>
  
  
</ul>


<div id="mobile-nav" class="side-nav sidenav">

    <div class="mobile-head bg-color">
        
          <img src="/blog/medias/logo.png" class="logo-img circle responsive-img">
        
        <div class="logo-name">熊猫小二</div>
        <div class="logo-desc">
            
            熊猫小二的博客  xmxe&#39;s blog
            
        </div>
    </div>

    

    <ul class="menu-list mobile-menu-list">
        
        <li class="m-nav-item">
	  
		<a href="/blog/" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-home"></i>
			
			首页
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="javascript:;">
			
				<i class="fa-fw fas fa-book"></i>
			
			归档
			<span class="m-icon"><i class="fas fa-chevron-right"></i></span>
		</a>
            <ul  >
              
                <li>

                  <a href="/blog/archives " style="margin-left:75px">
				  
				   <i class="fa fas fa-archive" style="position: absolute;left:50px" ></i>
			      
		          <span>归档</span>
                  </a>
                </li>
              
                <li>

                  <a href="/blog/tags " style="margin-left:75px">
				  
				   <i class="fa fas fa-tags" style="position: absolute;left:50px" ></i>
			      
		          <span>标签</span>
                  </a>
                </li>
              
                <li>

                  <a href="/blog/categories " style="margin-left:75px">
				  
				   <i class="fa fas fa-bookmark" style="position: absolute;left:50px" ></i>
			      
		          <span>分类</span>
                  </a>
                </li>
              
            </ul>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="/blog/friends" class="waves-effect waves-light">
			
			    <i class="fa-fw fas fa-address-book"></i>
			
			友链
		</a>
          
        </li>
        
        <li class="m-nav-item">
	  
		<a href="javascript:;">
			
				<i class="fa-fw fas fa-user-circle"></i>
			
			关于
			<span class="m-icon"><i class="fas fa-chevron-right"></i></span>
		</a>
            <ul  >
              
                <li>

                  <a href="/blog/about " style="margin-left:75px">
				  
				   <i class="fa fas fa-star-of-david" style="position: absolute;left:50px" ></i>
			      
		          <span>主页</span>
                  </a>
                </li>
              
                <li>

                  <a href="/blog/gallery " style="margin-left:75px">
				  
				   <i class="fa fas fa-images" style="position: absolute;left:50px" ></i>
			      
		          <span>相册</span>
                  </a>
                </li>
              
            </ul>
          
        </li>
        
        
    </ul>
</div>


        </div>

        
    </nav>

</header>

    
<script src="/blog/libs/cryptojs/crypto-js.min.js"></script>
<script>
    (function() {
        let pwd = '';
        if (pwd && pwd.length > 0) {
            if (pwd !== CryptoJS.SHA256(prompt('请输入访问本文章的密码')).toString(CryptoJS.enc.Hex)) {
                alert('密码错误，将返回主页！');
                location.href = '/blog/';
            }
        }
    })();
</script>




<div class="bg-cover pd-header post-cover" style="background-image: url('/blog/medias/featureimages/5.jpg')">
    <div class="container" style="right: 0px;left: 0px;">
        <div class="row">
            <div class="col s12 m12 l12">
                <div class="brand">
                    <h1 id="post-title" class="description center-align post-title"></h1>

                    
                        <!-- <script src="https://cdn.jsdelivr.net/npm/typed.js@2.0.11"></script> -->
                        <script>
                            var typedObj = new Typed("#post-title", {
                                strings: [ 'kubernetes' ],
                                startDelay: 300,
                                typeSpeed: 70,
                                loop: false,
                                backSpeed: 50,
                                showCursor: true
                            });
                        </script>
                    
                </div>
            </div>
        </div>
    </div>
</div>




<main class="post-container content">

    
    <link rel="stylesheet" href="/blog/libs/tocbot/tocbot.css">
<style>
    #articleContent h1::before,
    #articleContent h2::before,
    #articleContent h3::before,
    #articleContent h4::before,
    #articleContent h5::before,
    #articleContent h6::before {
        display: block;
        content: " ";
        height: 100px;
        margin-top: -100px;
        visibility: hidden;
    }

    #articleContent :focus {
        outline: none;
    }

    .toc-fixed {
        position: fixed;
        top: 64px;
    }

    .toc-widget {
        width: 345px;
        padding-left: 20px;
        background-color: rgb(255, 255, 255,0.7);
        border-radius: 10px;
        box-shadow: 0 10px 35px 2px rgba(0, 0, 0, .15), 0 5px 15px rgba(0, 0, 0, .07), 0 2px 5px -5px rgba(0, 0, 0, .1) !important;
    }

    .toc-widget .toc-title {
        padding: 35px 0 15px 17px;
        font-size: 1.5rem;
        font-weight: bold;
        line-height: 1.5rem;
    }

    .toc-widget ol {
        padding: 0;
        list-style: none;
    }

    #toc-content {
        padding-bottom: 30px;
        overflow: auto;
        max-height: 480px;
    }

    #toc-content ol {
        padding-left: 10px;
    }

    #toc-content ol li {
        padding-left: 10px;
    }

    #toc-content .toc-link:hover {
        color: #42b983;
        font-weight: 700;
        text-decoration: underline;
    }

    #toc-content .toc-link::before {
        background-color: transparent;
        max-height: 25px;

        position: absolute;
        right: 23.5vw;
        display: block;
    }

    #toc-content .is-active-link {
        color: #42b983;
    }

    #floating-toc-btn {
        position: fixed;
        right: 15px;
        bottom: 76px;
        padding-top: 15px;
        margin-bottom: 0;
        z-index: 998;
    }

    #floating-toc-btn .btn-floating {
        width: 48px;
        height: 48px;
    }

    #floating-toc-btn .btn-floating i {
        line-height: 48px;
        font-size: 1.4rem;
    }
</style>
<div class="row">
    <div id="main-content" class="col s12 m12 l9">
        <!-- 文章内容详情 -->
<div id="artDetail">
    <div class="card">
        <div class="card-content article-info">
            <div class="row tag-cate">
                <div class="col s7">
                    
                          <div class="article-tag">
                            <span class="chip bg-color">无标签</span>
                          </div>
                    
                </div>
                <div class="col s5 right-align">
                    
                    <div class="post-cate">
                        <i class="fas fa-bookmark fa-fw icon-category"></i>
                        
                            <a href="/blog/categories/%E6%8A%80%E6%9C%AF%E6%A0%88/" class="post-category">
                                技术栈
                            </a>
                        
                    </div>
                    
                </div>
            </div>

            <div class="post-info">
                

                

                

                

                
            </div>
        </div>
        <hr class="clearfix">

        
        <!-- 是否加载使用自带的 prismjs. -->
        <link rel="stylesheet" href="/blog/libs/prism/prism.css">
        

        
        <!-- 代码块折行 -->
        <style type="text/css">
            code[class*="language-"], pre[class*="language-"] { white-space: pre-wrap !important; }
        </style>
        

        <div class="card-content article-card-content">
            <div id="articleContent">
                <h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><p>在开始本教程之前，需要配置好本地环境，以下是需要安装的依赖和包。</p>
<h3 id="安装docker"><a href="#安装docker" class="headerlink" title="安装docker"></a>安装docker</h3><p>首先我们需要安装<code>docker</code>来打包镜像，如果你本地已经安装了<code>docker</code>，那么你可以选择跳过这一小节。</p>
<h4 id="推荐安装方法"><a href="#推荐安装方法" class="headerlink" title="推荐安装方法"></a>推荐安装方法</h4><p>目前使用<a target="_blank" rel="noopener" href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a>来安装docker，打开<a target="_blank" rel="noopener" href="https://www.docker.com/products/docker-desktop/">官网</a>下载对应你电脑操作系统的包即可。当安装完成后，可以通过<code>docker run hello-world</code>来快速校验是否安装成功！</p>
<h4 id="其它安装方法"><a href="#其它安装方法" class="headerlink" title="其它安装方法"></a>其它安装方法</h4><p>目前Docker公司宣布<a target="_blank" rel="noopener" href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a>只对个人开发者或者小型团体免费(2021年起对大型公司不再免费)，所以如果你不能通过<a target="_blank" rel="noopener" href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a>的方式下载安装<code>docker</code>，可以参考<a target="_blank" rel="noopener" href="https://dhwaneetbhatt.com/blog/run-docker-without-docker-desktop-on-macos">这篇文章</a>只安装<a target="_blank" rel="noopener" href="https://github.com/docker/cli">Docker CLI</a>。</p>
<h3 id="安装minikube"><a href="#安装minikube" class="headerlink" title="安装minikube"></a>安装minikube</h3><p>我们还需要搭建一套k8s本地集群(使用云厂商或者其它k8s集群都可)。本地搭建k8s集群的方式推荐使用<a target="_blank" rel="noopener" href="https://minikube.sigs.k8s.io/docs/">minikube</a>。可以根据<a target="_blank" rel="noopener" href="https://minikube.sigs.k8s.io/docs/start/">minikube快速安装</a>来进行下载安装，这里简单列举MacOS的安装方式，Linux&amp;Windows操作系统可以参考<a target="_blank" rel="noopener" href="https://minikube.sigs.k8s.io/docs/start/">官方文档</a>快速安装。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">brew <span class="token function">install</span> minikube<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<h4 id="启动minikube"><a href="#启动minikube" class="headerlink" title="启动minikube"></a>启动minikube</h4><p>因为minikube支持很多容器和虚拟化技术(<a target="_blank" rel="noopener" href="https://minikube.sigs.k8s.io/docs/drivers/docker/">Docker</a>,<a target="_blank" rel="noopener" href="https://minikube.sigs.k8s.io/docs/drivers/hyperkit/">Hyperkit</a>,<a target="_blank" rel="noopener" href="https://minikube.sigs.k8s.io/docs/drivers/hyperv/">Hyper-V</a>,<a target="_blank" rel="noopener" href="https://minikube.sigs.k8s.io/docs/drivers/kvm2/">KVM</a>,<a target="_blank" rel="noopener" href="https://minikube.sigs.k8s.io/docs/drivers/parallels/">Parallels</a>,<a target="_blank" rel="noopener" href="https://minikube.sigs.k8s.io/docs/drivers/podman/">Podman</a>,<a target="_blank" rel="noopener" href="https://minikube.sigs.k8s.io/docs/drivers/virtualbox/">VirtualBox</a>,<a target="_blank" rel="noopener" href="https://minikube.sigs.k8s.io/docs/drivers/vmware/">VMware Fusion&#x2F;Workstation</a>)，也是问题出现比较多的地方，所以这里还是稍微说明一下。如果你使用<code>docker</code>的方案是上面推荐的<a target="_blank" rel="noopener" href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a>，那么你以下面的命令启动minikube即可，需要耐心等待下载依赖。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">minikube start --vm-driver <span class="token function">docker</span> --container-runtime<span class="token operator">=</span>docker<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>启动完成后，运行<code>minikube status</code>查看当前状态确定是否启动成功！</p>
<p>如果你本地只有Docker CLI，判断标准如果执行<code>docker ps</code>等命令，返回错误<code>Cannot connect to the Docker daemon at unix:///Users/xxxx/.colima/docker.sock. Is the docker daemon running?</code>那么就需要操作下面的命令。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">brew <span class="token function">install</span> hyperkit
minikube start --vm-driver hyperkit --container-runtime<span class="token operator">=</span>docker

<span class="token comment"># Tell Docker CLI to talk to minikube's VM</span>
<span class="token builtin class-name">eval</span> <span class="token variable"><span class="token variable">$(</span>minikube docker-env<span class="token variable">)</span></span>

<span class="token comment"># Save IP to a hostname</span>
<span class="token builtin class-name">echo</span> <span class="token string">"<span class="token variable"><span class="token variable">`</span>minikube <span class="token function">ip</span><span class="token variable">`</span></span> docker.local"</span> <span class="token operator">|</span> <span class="token function">sudo</span> <span class="token function">tee</span> <span class="token parameter variable">-a</span> /etc/hosts <span class="token operator">></span> /dev/null

<span class="token comment"># Test</span>
<span class="token function">docker</span> run hello-world<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h4 id="minikube命令速查"><a href="#minikube命令速查" class="headerlink" title="minikube命令速查"></a>minikube命令速查</h4><pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 不会删除任何数据，只是停止VM和k8s集群。</span>
minikube stop
<span class="token comment"># 删除所有minikube启动后的数据。</span>
minikube delete
<span class="token comment"># 查看集群和docker enginer运行的IP地址。</span>
minikube <span class="token function">ip</span>
<span class="token comment"># 暂停当前的资源和k8s集群</span>
minikube pause
<span class="token comment"># 查看当前集群状态</span>
minikube status<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="安装kubectl"><a href="#安装kubectl" class="headerlink" title="安装kubectl"></a>安装kubectl</h3><p>这一步是可选的，如果不安装的话，后续所有<code>kubectl</code>相关的命令，使用<code>minikube kubectl</code>命令替代即可。如果你不想使用<code>minikube kubectl</code>或者配置相关环境变量来进行下面的教学的话，可以考虑直接安装<code>kubectl</code>。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">brew <span class="token function">install</span> kubectl<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<h3 id="注册Docker-Hub账号"><a href="#注册Docker-Hub账号" class="headerlink" title="注册Docker Hub账号"></a>注册Docker Hub账号</h3><p>因为默认minikube使用的镜像地址是DockerHub，所以我们还需要在<a target="_blank" rel="noopener" href="https://hub.docker.com/">DockerHub</a>中注册账号，并且使用login命令登录账号。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">docker</span> login<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<h2 id="Container"><a href="#Container" class="headerlink" title="Container"></a>Container</h2><p>我们的旅程从一段代码开始。新建一个<code>main.go</code>文件，复制下面的代码到文件中。</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> main

<span class="token keyword">import</span> <span class="token punctuation">(</span>
	<span class="token string">"io"</span>
	<span class="token string">"net/http"</span>
<span class="token punctuation">)</span>

<span class="token keyword">func</span> <span class="token function">hello</span><span class="token punctuation">(</span>w http<span class="token punctuation">.</span>ResponseWriter<span class="token punctuation">,</span> r <span class="token operator">*</span>http<span class="token punctuation">.</span>Request<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	io<span class="token punctuation">.</span><span class="token function">WriteString</span><span class="token punctuation">(</span>w<span class="token punctuation">,</span> <span class="token string">"[v1] Hello, Kubernetes!"</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span>

<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	http<span class="token punctuation">.</span><span class="token function">HandleFunc</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">,</span> hello<span class="token punctuation">)</span>
	http<span class="token punctuation">.</span><span class="token function">ListenAndServe</span><span class="token punctuation">(</span><span class="token string">":3000"</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>上面是一串用<a target="_blank" rel="noopener" href="https://go.dev/">Go</a>写的代码，代码逻辑非常的简单，首先启动HTTP服务器，监听<code>3000</code>端口，当访问路由<code>/</code>的时候返回字符串<code>[v1] Hello, Kubernetes!</code>。在以前，如果你想将这段代码运行起来并测试一下。你首先需要懂得如何下载golang的安装包进行安装，接着需要懂得<code>golang module</code>的基本使用，最后还需要了解golang的编译和运行命令，才能将该代码运行起来。甚至在过程中，可能会因为环境变量问题、操作系统问题、处理器架构等问题导致编译或运行失败。但是通过Container(容器)技术，只需要上面的代码，附带着对应的容器<code>Dockerfile</code>文件，那么你就不需要golang的任何知识，也能将代码顺利运行起来。</p>
<blockquote>
<p>Container(容器)是一种沙盒技术。它是基于Linux中Namespace&#x2F;Cgroups&#x2F;chroot等技术组合而成，更多技术细节可以参照这个视频<a target="_blank" rel="noopener" href="https://www.youtube.com/watch?v=8fi7uSYlOdc">如何自己实现一个容器</a>。</p>
</blockquote>
<p>下面就是Go代码对应的<code>Dockerfile</code>，简单的方案是直接使用golang的alpine镜像来打包，但是因为我们后续练习需要频繁的推送镜像到Docker Hub和拉取镜像到k8s集群中，为了优化网络速度，我们选择先在<code>golang:1.16-buster</code>中将上述Go代码编译成二进制文件，再将二进制文件复制到<code>base-debian10</code>镜像中运行。这样我们可以将300MB大小的镜像变成只有20MB的镜像，甚至压缩上传到DockerHub后大小只有10MB！</p>
<pre class="line-numbers language-docker" data-language="docker"><code class="language-docker"><span class="token comment"># Dockerfile</span>
<span class="token instruction"><span class="token keyword">FROM</span> golang:1.16-buster <span class="token keyword">AS</span> builder</span>
<span class="token instruction"><span class="token keyword">RUN</span> mkdir /src</span>
<span class="token instruction"><span class="token keyword">ADD</span> . /src</span>
<span class="token instruction"><span class="token keyword">WORKDIR</span> /src</span>

<span class="token instruction"><span class="token keyword">RUN</span> go env -w GO111MODULE=auto</span>
<span class="token instruction"><span class="token keyword">RUN</span> go build -o main .</span>

<span class="token instruction"><span class="token keyword">FROM</span> gcr.io/distroless/base-debian10</span>

<span class="token instruction"><span class="token keyword">WORKDIR</span> /</span>

<span class="token instruction"><span class="token keyword">COPY</span> <span class="token options"><span class="token property">--from</span><span class="token punctuation">=</span><span class="token string">builder</span></span> /src/main /main</span>
<span class="token instruction"><span class="token keyword">EXPOSE</span> 3000</span>
<span class="token instruction"><span class="token keyword">ENTRYPOINT</span> [<span class="token string">"/main"</span>]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>需要注意<code>main.go</code>文件需要和<code>Dockerfile</code>文件在同一个目录下，执行下方<code>docker build</code>命令，第一次需要耐心等待拉取基础镜像。并且<strong>需要注意将命令中<code>guangzhengli</code>替换成自己的<code>DockerHub</code>注册的账号名称</strong>。这样我们后续可以推送镜像到自己注册的<code>DockerHub</code>仓库当中。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">docker</span> build <span class="token builtin class-name">.</span> <span class="token parameter variable">-t</span> guangzhengli/hellok8s:v1
<span class="token comment"># Step 1/11 : FROM golang:1.16-buster AS builder</span>
<span class="token comment"># ...</span>
<span class="token comment"># Successfully tagged guangzhengli/hellok8s:v1</span>

<span class="token function">docker</span> images
<span class="token comment"># guangzhengli/hellok8s          v1         f956e8cf7d18   8 days ago      25.4MB</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><code>docker build</code>命令完成后我们可以通过<code>docker images</code>命令查看镜像是否build成功，最后我们执行<code>docker run</code>命令将容器启动，<code>-p</code>指定<code>3000</code>作为端口，<code>-d</code>指定容器后台运行。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">docker</span> run <span class="token parameter variable">-p</span> <span class="token number">3000</span>:3000 <span class="token parameter variable">--name</span> hellok8s <span class="token parameter variable">-d</span> guangzhengli/hellok8s:v1<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>运行成功后，可以通过浏览器或者<code>curl</code>来访问<code>http://127.0.0.1:3000</code>,查看是否成功返回字符串<code>[v1] Hello, Kubernetes!</code>。这里因为我本地只用Docker CLI，而docker runtime是使用<code>minikube</code>，所以我需要先调用<code>minikube ip</code>来返回minikube IP地址，例如返回了<code>192.168.59.100</code>，所以我需要访问<code>http://192.168.59.100:3000</code>来判断是否成功返回字符串<code>[v1] Hello, Kubernetes!</code>。最后确认没有问题，使用<code>docker push</code>将镜像上传到远程的<code>DockerHub</code>仓库当中，这样可以供他人下载使用，也方便后续<code>Minikube</code>下载镜像使用。<strong>需要注意将<code>guangzhengli</code>替换成自己的<code>DockerHub</code>账号名称</strong>。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">docker</span> push guangzhengli/hellok8s:v1<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<h2 id="Pod"><a href="#Pod" class="headerlink" title="Pod"></a>Pod</h2><p>如果在生产环境中运行的都是独立的单体服务，那么Container(容器)也就够用了，但是在实际的生产环境中，维护着大规模的集群和各种不同的服务，服务之间往往存在着各种各样的关系。而这些关系的处理，才是手动管理最困难的地方。<strong>Pod</strong>是我们将要创建的第一个k8s资源，也是可以在Kubernetes中创建和管理的、最小的可部署的计算单元。在了解<code>pod</code>和<code>container</code>的区别之前，我们可以先创建一个简单的pod试试，我们先创建<code>nginx.yaml</code>文件，编写一个可以创建<code>nginx</code>的Pod。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token comment"># nginx.yaml</span>
<span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Pod <span class="token comment"># 表示我们要创建的资源是Pod类型</span>
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> nginx<span class="token punctuation">-</span>pod <span class="token comment"># 这个名字需要是唯一</span>
<span class="token key atrule">spec</span><span class="token punctuation">:</span> <span class="token comment"># 要运行的容器的名称和镜像名称</span>
  <span class="token key atrule">containers</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> nginx<span class="token punctuation">-</span>container
      <span class="token key atrule">image</span><span class="token punctuation">:</span> nginx<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>其中<code>kind</code>表示我们要创建的资源是<code>Pod</code>类型，<code>metadata.name</code>表示要创建的pod的名字，这个名字需要是唯一的。<code>spec.containers</code>表示要运行的容器的名称和镜像名称。镜像默认来源<code>DockerHub</code>。我们运行第一条k8s命令<code>kubectl apply -f nginx.yaml</code>命令来创建<code>nginx</code>Pod。接着通过<code>kubectl get pods</code>来查看pod是否正常启动。最后通过<code>kubectl port-forward nginx-pod 4000:80</code>命令将<code>nginx</code>默认的<code>80</code>端口映射到本机的<code>4000</code>端口，打开浏览器或者<code>curl</code>来访问<code>http://127.0.0.1:4000</code>,查看是否成功访问<code>nginx</code>默认页面！</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl apply <span class="token parameter variable">-f</span> nginx.yaml
<span class="token comment"># pod/nginx-pod created</span>

kubectl get pods
<span class="token comment"># nginx-pod         1/1     Running   0           6s</span>

kubectl port-forward nginx-pod <span class="token number">4000</span>:80
<span class="token comment"># Forwarding from 127.0.0.1:4000 -> 80</span>
<span class="token comment"># Forwarding from [::1]:4000 -> 80</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><code>kubectl exec -it</code>可以用来进入Pod内容器的Shell。通过命令下面的命令来配置<code>nginx</code>的首页内容。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl <span class="token builtin class-name">exec</span> <span class="token parameter variable">-it</span> nginx-pod /bin/bash

<span class="token builtin class-name">echo</span> <span class="token string">"hello kubernetes by nginx!"</span> <span class="token operator">></span> /usr/share/nginx/html/index.html

kubectl port-forward nginx-pod <span class="token number">4000</span>:80<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>最后可以通过浏览器或者<code>curl</code>来访问<code>http://127.0.0.1:4000</code>,查看是否成功启动<code>nginx</code>和返回字符串<code>hello kubernetes by nginx!</code>。</p>
<h3 id="Pod与Container的不同"><a href="#Pod与Container的不同" class="headerlink" title="Pod与Container的不同"></a>Pod与Container的不同</h3><p>回到<code>pod</code>和<code>container</code>的区别，我们会发现刚刚创建出来的资源如下图所示，在最内层是我们的服务<code>nginx</code>，运行在<code>container</code>容器当中，<code>container</code>(容器)的本质是进程，而<code>pod</code>是管理这一组进程的资源。</p>
<p><img src="https://cdn.jsdelivr.net/gh/guangzhengli/PicURL@master/uPic/nginx_pod.png" alt="nginx_pod"></p>
<p>所以自然<code>pod</code>可以管理多个<code>container</code>，在某些场景例如服务之间需要文件交换(日志收集)，本地网络通信需求(使用localhost或者Socket文件进行本地通信)，在这些场景中使用<code>pod</code>管理多个<code>container</code>就非常的推荐。而这，也是k8s如何处理服务之间复杂关系的第一个例子，如下图所示：</p>
<p><img src="https://cdn.jsdelivr.net/gh/guangzhengli/PicURL@master/uPic/pod.png" alt="pod"></p>
<p><strong>为什么Kubernetes要设计Pod这个概念？（为什么区分？）</strong></p>
<p>这是理解Kubernetes架构的关键。主要原因有以下几点：</p>
<ol>
<li>管理“亲密关系”容器</li>
</ol>
<p>有些应用由多个紧密耦合、需要共享资源的进程组成。在Docker的世界里，你可能会把所有进程塞进一个容器（违背了“一个容器一个进程”的最佳实践），或者使用 <code>--net=container:&lt;id&gt;</code> 等复杂方式连接多个容器。</p>
<p><strong>Pod 提供了一个优雅的解决方案</strong>，它将这些紧密关联的容器组合成一个逻辑单元，让它们：</p>
<ul>
<li><strong>共享网络</strong>：Pod内的容器可以通过<code>localhost</code>直接通信，因为它们共享同一个IP地址。</li>
<li><strong>共享存储</strong>：可以定义一个存储卷，并将其挂载到Pod内的多个容器中，让它们共享文件。</li>
</ul>
<p><strong>经典例子：Sidecar模式</strong><br>一个 Pod 包含两个容器：</p>
<ul>
<li><strong>主容器</strong>：<code>web-server</code>，提供Web服务。</li>
<li><strong>边车容器</strong>：<code>log-collector</code>，负责收集主容器产生的日志文件，并上传到中央日志系统。<br>这两个容器需要共享同一个日志目录（存储卷），并且<code>log-collector</code>可能需要通过<code>localhost</code>访问<code>web-server</code>的健康检查接口。Pod完美地满足了这种需求。</li>
<li><strong>容器也能共享资源，为什么还要pod？因为Pod确保了一组容器总是被调度到同一个节点上，如果分布式系统在不同节点上基本上无法实现共享资源</strong></li>
</ul>
<ol start="2">
<li>抽象与简化</li>
</ol>
<p>Kubernetes的目标是管理分布式系统，而不是直接管理成千上万个独立的容器。通过将容器分组到Pod中，Kubernetes的调度器、控制器和API只需要与Pod这个更高级的抽象进行交互，大大简化了系统的复杂性。</p>
<ul>
<li><strong>调度</strong>：Kubernetes调度的是整个Pod，而不是单个容器。这意味着Pod内的所有容器总是被调度到<strong>同一个节点</strong>上，确保了它们可以共享资源。</li>
<li><strong>生命周期管理</strong>：Kubernetes以Pod为单位进行扩缩容、滚动更新和健康检查。</li>
</ul>
<ol start="3">
<li>提供更丰富的上下文</li>
</ol>
<p>Pod为容器提供了额外的元数据和环境，例如：</p>
<ul>
<li><strong>标签和注解</strong>：可以附加到Pod上，用于服务发现、负载均衡和自动化。</li>
<li><strong>安全上下文</strong>：可以定义在Pod级别，应用于其内部的所有容器。</li>
<li><strong>资源限制</strong>：可以在Pod级别定义，限制整个Pod所能使用的CPU和内存总量。</li>
</ul>
<h3 id="Pod其它命令"><a href="#Pod其它命令" class="headerlink" title="Pod其它命令"></a>Pod其它命令</h3><p>我们可以通过<code>logs</code>或者<code>logs -f</code>命令查看pod日志，可以通过<code>exec -it</code>进入pod或者调用容器命令，通过<code>delete pod</code>或者<code>delete -f nginx.yaml</code>的方式删除pod资源。这里可以看到<a target="_blank" rel="noopener" href="https://kubernetes.io/docs/reference/kubectl/cheatsheet/">kubectl所有命令</a>。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl logs <span class="token parameter variable">--follow</span> nginx-pod      
kubectl <span class="token builtin class-name">exec</span> nginx-pod -- <span class="token function">ls</span>
kubectl delete pod nginx-pod
<span class="token comment"># pod "nginx-pod" deleted</span>
kubectl delete <span class="token parameter variable">-f</span> nginx.yaml
<span class="token comment"># pod "nginx-pod" deleted</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><code>hellok8s:v1</code>Pod资源定义和相应的命令如下所示：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token comment"># hellok8s.yaml</span>
<span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Pod
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">containers</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>container
      <span class="token key atrule">image</span><span class="token punctuation">:</span> guangzhengli/hellok8s<span class="token punctuation">:</span>v1<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl apply <span class="token parameter variable">-f</span> hellok8s.yaml
kubectl get pods
kubectl port-forward hellok8s <span class="token number">3000</span>:3000<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<h2 id="Deployment"><a href="#Deployment" class="headerlink" title="Deployment"></a>Deployment</h2><p>在生产环境中，我们基本上不会直接管理pod，我们需要<code>kubernetes</code>来帮助我们来完成一些自动化操作，例如自动扩容或者自动升级版本。可以想象在生产环境中，我们手动部署了10个<code>hellok8s:v1</code>的pod，这个时候我们需要升级成<code>hellok8s:v2</code>版本，我们难道需要一个一个的将<code>hellok8s:v1</code>的pod手动升级吗？这个时候就需要我们来看<code>kubernetes</code>的另外一个资源<code>deployment</code>，来帮助我们管理pod。简单来说，<strong>Deployment是Kubernetes中用于管理Pod和ReplicaSet的一种高级抽象，它为我们提供了一种声明式的方式来定义和控制应用程序的部署和更新。Deployment管理的是具有相同标签的「一组Pod」，而不是所有Pod</strong></p>
<h3 id="扩容"><a href="#扩容" class="headerlink" title="扩容"></a>扩容</h3><p>这是通过一个叫做<strong>ReplicaSet</strong>的底层对象来实现的。Deployment会自动创建并管理ReplicaSet。</p>
<ul>
<li><strong>ReplicaSet</strong>的唯一职责就是确保指定数量的、完全相同的Pod副本始终处于运行状态。</li>
<li>如果某个Pod因为任何原因（节点故障、容器崩溃等）消失了，ReplicaSet会立刻察觉到并创建一个新的Pod来替代它。</li>
<li><strong>关系链：<code>Deployment</code> -&gt; 管理<code>ReplicaSet</code> -&gt; 管理<code>Pod</code></strong></li>
</ul>
<p>首先可以创建一个<code>deployment.yaml</code>的文件。来管理<code>hellok8s</code>pod。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>deployment
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">3</span> <span class="token comment"># 希望始终有3个Pod在运行</span>
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>
      <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
  <span class="token key atrule">template</span><span class="token punctuation">:</span>
    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>
      <span class="token key atrule">labels</span><span class="token punctuation">:</span>
        <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
    <span class="token key atrule">spec</span><span class="token punctuation">:</span>
      <span class="token key atrule">containers</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> guangzhengli/hellok8s<span class="token punctuation">:</span>v1
          <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>container<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>其中<code>kind</code>表示我们要创建的资源是<code>deployment</code>类型，<code>metadata.name</code>表示要创建的deployment的名字，这个名字需要是<strong>唯一</strong>的。在<code>spec</code>里面表示，首先<code>replicas</code>表示的是部署的pod副本数量，<code>selector</code>里面表示的是<code>deployment</code>资源和<code>pod</code>资源关联的方式，这里表示<code>deployment</code>会管理(selector)所有<code>labels=hellok8s</code>的pod。<code>template</code>的内容是用来定义<code>pod</code>资源的，你会发现和Hellok8s Pod资源的定义是差不多的，唯一的区别是我们需要加上<code>metadata.labels</code>来和上面的<code>selector.matchLabels</code>对应起来。来表明pod是被deployment管理，不用在<code>template</code>里面加上<code>metadata.name</code>是因为deployment会自动为我们创建pod的唯一<code>name</code>。接下来输入下面的命令，可以创建<code>deployment</code>资源。通过<code>get</code>和<code>delete pod</code>命令，我们会初步感受deployment的魅力。<strong>每次创建的pod名称都会变化，某些命令记得替换成你的pod的名称</strong></p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl apply <span class="token parameter variable">-f</span> deployment.yaml

kubectl get deployments
<span class="token comment">#NAME                  READY   UP-TO-DATE   AVAILABLE   AGE</span>
<span class="token comment">#hellok8s-deployment   1/1     1            1           39s</span>

kubectl get pods             
<span class="token comment">#NAME                                   READY   STATUS    RESTARTS   AGE</span>
<span class="token comment">#hellok8s-deployment-77bffb88c5-qlxss   1/1     Running   0          119s</span>

kubectl delete pod hellok8s-deployment-77bffb88c5-qlxss 
<span class="token comment">#pod "hellok8s-deployment-77bffb88c5-qlxss" deleted</span>

kubectl get pods                                       
<span class="token comment">#NAME                                   READY   STATUS    RESTARTS   AGE</span>
<span class="token comment">#hellok8s-deployment-77bffb88c5-xp8f7   1/1     Running   0          18s</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>我们会发现一个有趣的现象，当手动删除一个<code>pod</code>资源后，deployment会自动创建一个新的<code>pod</code>，这和我们之前手动创建pod资源有本质的区别！这代表着当生产环境管理着成千上万个pod时，我们不需要关心具体的情况，只需要维护好这份<code>deployment.yaml</code>文件的资源定义即可。接下来我们通过自动扩容来加深这个知识点，当我们想要将<code>hellok8s:v1</code>的资源扩容到5个副本时，只需要将<code>replicas</code>的值设置成3，接着重新输入<code>kubectl apply -f deployment.yaml</code>即可。如下所示：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>deployment
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">5</span> <span class="token comment"># 扩容5个pod</span>
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>
      <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
  <span class="token key atrule">template</span><span class="token punctuation">:</span>
    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>
      <span class="token key atrule">labels</span><span class="token punctuation">:</span>
        <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
    <span class="token key atrule">spec</span><span class="token punctuation">:</span>
      <span class="token key atrule">containers</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> guangzhengli/hellok8s<span class="token punctuation">:</span>v1
          <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>container<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>可以在<code>kubectl apply</code>之前通过新建窗口执行<code>kubectl get pods --watch</code>命令来观察pod启动和删除的记录，想要减少副本数时也很简单，你可以尝试将副本数随意增大或者缩小，再通过<code>watch</code>来观察它的状态。</p>
<p><img src="https://cdn.jsdelivr.net/gh/guangzhengli/PicURL@master/uPic/deployment.png" alt="deployment"></p>
<h3 id="升级版本"><a href="#升级版本" class="headerlink" title="升级版本"></a>升级版本</h3><p>我们接下来尝试将所有<code>v1</code>版本的<code>pod</code>升级到<code>v2</code>版本。首先我们需要构建一份<code>hellok8s:v2</code>的版本镜像。唯一的区别就是字符串替换成了<code>[v2] Hello, Kubernetes!</code>。</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> main

<span class="token keyword">import</span> <span class="token punctuation">(</span>
	<span class="token string">"io"</span>
	<span class="token string">"net/http"</span>
<span class="token punctuation">)</span>

<span class="token keyword">func</span> <span class="token function">hello</span><span class="token punctuation">(</span>w http<span class="token punctuation">.</span>ResponseWriter<span class="token punctuation">,</span> r <span class="token operator">*</span>http<span class="token punctuation">.</span>Request<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	io<span class="token punctuation">.</span><span class="token function">WriteString</span><span class="token punctuation">(</span>w<span class="token punctuation">,</span> <span class="token string">"[v2] Hello, Kubernetes!"</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span>

<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	http<span class="token punctuation">.</span><span class="token function">HandleFunc</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">,</span> hello<span class="token punctuation">)</span>
	http<span class="token punctuation">.</span><span class="token function">ListenAndServe</span><span class="token punctuation">(</span><span class="token string">":3000"</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>将 <code>hellok8s:v2</code> 推到 DockerHub 仓库中。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">docker</span> build <span class="token builtin class-name">.</span> <span class="token parameter variable">-t</span> guangzhengli/hellok8s:v2
<span class="token function">docker</span> push guangzhengli/hellok8s:v2<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<p>接着编写<code>v2</code>版本的deployment资源文件。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>deployment
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">3</span>
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>
      <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
  <span class="token key atrule">template</span><span class="token punctuation">:</span>
    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>
      <span class="token key atrule">labels</span><span class="token punctuation">:</span>
        <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
    <span class="token key atrule">spec</span><span class="token punctuation">:</span>
      <span class="token key atrule">containers</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> guangzhengli/hellok8s<span class="token punctuation">:</span>v2 <span class="token comment"># 镜像修改为v2</span>
          <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>container<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl apply <span class="token parameter variable">-f</span> deployment.yaml
<span class="token comment"># deployment.apps/hellok8s-deployment configured</span>

kubectl get pods                
<span class="token comment"># NAME                                   READY   STATUS    RESTARTS   AGE</span>
<span class="token comment"># hellok8s-deployment-66799848c4-kpc6q   1/1     Running   0          3s</span>
<span class="token comment"># hellok8s-deployment-66799848c4-pllj6   1/1     Running   0          3s</span>
<span class="token comment"># hellok8s-deployment-66799848c4-r7qtg   1/1     Running   0          3s</span>

kubectl port-forward hellok8s-deployment-66799848c4-kpc6q <span class="token number">3000</span>:3000
<span class="token comment"># Forwarding from 127.0.0.1:3000 -> 3000</span>
<span class="token comment"># Forwarding from [::1]:3000 -> 3000</span>

<span class="token comment"># open another terminal</span>
<span class="token function">curl</span> http://localhost:3000
<span class="token comment"># [v2] Hello, Kubernetes!</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>你也可以输入<code>kubectl describe pod hellok8s-deployment-66799848c4-kpc6q</code>来看是否是<code>v2</code>版本的镜像。</p>
<h3 id="Rolling-Update-滚动更新"><a href="#Rolling-Update-滚动更新" class="headerlink" title="Rolling Update(滚动更新)"></a>Rolling Update(滚动更新)</h3><p>如果我们在生产环境上，管理着多个副本的<code>hellok8s:v1</code>版本的pod，我们需要更新到<code>v2</code>的版本，像上面那样的部署方式是可以的，但是也会带来一个问题，就是所有的副本在同一时间更新，这会导致我们<code>hellok8s</code>服务在短时间内是不可用的，因为所有pod都在升级到<code>v2</code>版本的过程中，需要等待某个pod升级完成后才能提供服务。这个时候我们就需要滚动更新(rolling update)，在保证新版本<code>v2</code>的pod还没有<code>ready</code>之前，先不删除<code>v1</code>版本的pod。在deployment的资源定义中,<code>spec.strategy.type</code>有两种选择:</p>
<ul>
<li><strong>RollingUpdate</strong>：逐渐增加新版本的pod，逐渐减少旧版本的pod。</li>
<li><strong>Recreate</strong>：在新版本的pod增加前，先将所有旧版本pod删除。</li>
</ul>
<p>大多数情况下我们会采用滚动更新(RollingUpdate)的方式，滚动更新又可以通过<code>maxSurge</code>和<code>maxUnavailable</code>字段来控制升级pod的速率，具体可以详细看<a target="_blank" rel="noopener" href="https://kubernetes.io/zh-cn/docs/concepts/workloads/controllers/deployment/">官网定义</a>。：</p>
<ul>
<li><a target="_blank" rel="noopener" href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#max-surge"><strong>maxSurge:</strong></a>最大峰值，用来指定可以创建的超出期望Pod个数的Pod数量。</li>
<li><a target="_blank" rel="noopener" href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#max-unavailable,"><strong>maxUnavailable:</strong></a>最大不可用，用来指定更新过程中不可用的Pod的个数上限。</li>
</ul>
<p>我们先输入命令回滚我们的deployment，输入<code>kubectl describe pod</code>会发现deployment已经把<code>v2</code>版本的pod回滚到<code>v1</code>的版本。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl rollout undo deployment hellok8s-deployment

kubectl get pods                                    
<span class="token comment"># NAME                                   READY   STATUS    RESTARTS   AGE</span>
<span class="token comment"># hellok8s-deployment-77bffb88c5-cvm5c   1/1     Running   0          39s</span>
<span class="token comment"># hellok8s-deployment-77bffb88c5-lktbl   1/1     Running   0          41s</span>
<span class="token comment"># hellok8s-deployment-77bffb88c5-nh82z   1/1     Running   0          37s</span>

kubectl describe pod hellok8s-deployment-77bffb88c5-cvm5c
<span class="token comment"># Image: guangzhengli/hellok8s:v1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>除了上面的命令，还可以用<code>history</code>来查看历史版本，<code>--to-revision=2</code>来回滚到指定版本。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># 查看更新历史</span>
kubectl rollout <span class="token function">history</span> deployment hellok8s-deployment
<span class="token comment"># 回滚到上一个版本</span>
kubectl rollout undo deployment/nginx-deployment
<span class="token comment"># 回滚到指定版本</span>
kubectl rollout undo deployment/hellok8s-deployment --to-revision<span class="token operator">=</span><span class="token number">2</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>接着设置<code>strategy=rollingUpdate</code>,<code>maxSurge=1</code>,<code>maxUnavailable=1</code>和<code>replicas=3</code>到deployment.yaml文件中。这个参数配置意味着最大可能会创建4个hellok8s pod (replicas + maxSurge)，最小会有3个hellok8s pod存活(replicas-maxUnavailable)。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>deployment
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">strategy</span><span class="token punctuation">:</span>
     <span class="token key atrule">rollingUpdate</span><span class="token punctuation">:</span>
      <span class="token key atrule">maxSurge</span><span class="token punctuation">:</span> <span class="token number">1</span> <span class="token comment"># 在更新期间，可以比期望副本数多创建1个Pod</span>
      <span class="token key atrule">maxUnavailable</span><span class="token punctuation">:</span> <span class="token number">0</span> <span class="token comment"># 在更新期间，不允许有不可用的Pod（确保全时段服务）</span>
  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">3</span>
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>
      <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
  <span class="token key atrule">template</span><span class="token punctuation">:</span>
    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>
      <span class="token key atrule">labels</span><span class="token punctuation">:</span>
        <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
    <span class="token key atrule">spec</span><span class="token punctuation">:</span>
      <span class="token key atrule">containers</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> guangzhengli/hellok8s<span class="token punctuation">:</span>v2
        <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>container<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>使用<code>kubectl apply -f deployment.yaml</code>来重新创建<code>v2</code>的资源，可以通过<code>kubectl get pods --watch</code>来观察pod的创建销毁情况，是否如下图所示。</p>
<p><img src="https://cdn.jsdelivr.net/gh/guangzhengli/PicURL@master/uPic/rollingupdate.png" alt="rollingupdate"></p>
<p><strong>这个过程是：</strong></p>
<ol>
<li>根据新的Pod模板创建一个<strong>新的ReplicaSet</strong>。</li>
<li>新的ReplicaSet开始逐步创建新的Pod（<code>v2.0</code> 版本）。</li>
<li>同时，旧的ReplicaSet开始逐步终止旧的Pod（<code>v1.0</code> 版本）。</li>
<li>在整个过程中，始终有可用的Pod在处理用户请求，<strong>服务不会中断</strong>。</li>
</ol>
<h3 id="存活探针-livenessProb"><a href="#存活探针-livenessProb" class="headerlink" title="存活探针 (livenessProb)"></a>存活探针 (livenessProb)</h3><blockquote>
<p>存活探测器来确定什么时候要重启容器。例如，存活探测器可以探测到应用死锁（应用程序在运行，但是无法继续执行后面的步骤）情况。重启这种状态下的容器有助于提高应用的可用性，即使其中存在缺陷。– <a target="_blank" rel="noopener" href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/">LivenessProb</a></p>
</blockquote>
<p>在生产中，有时候因为某些bug导致应用死锁或者线程耗尽了，最终会导致应用无法继续提供服务，这个时候如果没有手段来自动监控和处理这一问题的话，可能会导致很长一段时间无人发现。<a target="_blank" rel="noopener" href="https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/">kubelet</a>使用存活探测器(livenessProb)来确定什么时候要重启容器。接下来我们写一个<code>/healthz</code>接口来说明<code>livenessProb</code>如何使用。<code>/healthz</code>接口会在启动成功的15s内正常返回200状态码，在15s后，会一直返回500的状态码。</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> main

<span class="token keyword">import</span> <span class="token punctuation">(</span>
	<span class="token string">"fmt"</span>
	<span class="token string">"io"</span>
	<span class="token string">"net/http"</span>
	<span class="token string">"time"</span>
<span class="token punctuation">)</span>

<span class="token keyword">func</span> <span class="token function">hello</span><span class="token punctuation">(</span>w http<span class="token punctuation">.</span>ResponseWriter<span class="token punctuation">,</span> r <span class="token operator">*</span>http<span class="token punctuation">.</span>Request<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	io<span class="token punctuation">.</span><span class="token function">WriteString</span><span class="token punctuation">(</span>w<span class="token punctuation">,</span> <span class="token string">"[v2] Hello, Kubernetes!"</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span>

<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	started <span class="token operator">:=</span> time<span class="token punctuation">.</span><span class="token function">Now</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	http<span class="token punctuation">.</span><span class="token function">HandleFunc</span><span class="token punctuation">(</span><span class="token string">"/healthz"</span><span class="token punctuation">,</span> <span class="token keyword">func</span><span class="token punctuation">(</span>w http<span class="token punctuation">.</span>ResponseWriter<span class="token punctuation">,</span> r <span class="token operator">*</span>http<span class="token punctuation">.</span>Request<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
		duration <span class="token operator">:=</span> time<span class="token punctuation">.</span><span class="token function">Since</span><span class="token punctuation">(</span>started<span class="token punctuation">)</span>
		<span class="token keyword">if</span> duration<span class="token punctuation">.</span><span class="token function">Seconds</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">></span> <span class="token number">15</span> <span class="token punctuation">&#123;</span>
			w<span class="token punctuation">.</span><span class="token function">WriteHeader</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span>
			w<span class="token punctuation">.</span><span class="token function">Write</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span>fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">"error: %v"</span><span class="token punctuation">,</span> duration<span class="token punctuation">.</span><span class="token function">Seconds</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
		<span class="token punctuation">&#125;</span> <span class="token keyword">else</span> <span class="token punctuation">&#123;</span>
			w<span class="token punctuation">.</span><span class="token function">WriteHeader</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span>
			w<span class="token punctuation">.</span><span class="token function">Write</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token function">byte</span><span class="token punctuation">(</span><span class="token string">"ok"</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
		<span class="token punctuation">&#125;</span>
	<span class="token punctuation">&#125;</span><span class="token punctuation">)</span>

	http<span class="token punctuation">.</span><span class="token function">HandleFunc</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">,</span> hello<span class="token punctuation">)</span>
	http<span class="token punctuation">.</span><span class="token function">ListenAndServe</span><span class="token punctuation">(</span><span class="token string">":3000"</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token comment"># Dockerfile</span>
FROM golang<span class="token punctuation">:</span>1.16<span class="token punctuation">-</span>buster AS builder
RUN mkdir /src
ADD . /src
WORKDIR /src

RUN go env <span class="token punctuation">-</span>w GO111MODULE=auto
RUN go build <span class="token punctuation">-</span>o main .

FROM gcr.io/distroless/base<span class="token punctuation">-</span>debian10

WORKDIR /

COPY <span class="token punctuation">-</span><span class="token punctuation">-</span>from=builder /src/main /main
EXPOSE 3000
ENTRYPOINT <span class="token punctuation">[</span><span class="token string">"/main"</span><span class="token punctuation">]</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><code>Dockerfile</code>的编写和原来保持一致，我们把<code>tag</code>修改为<code>liveness</code>并推送到远程仓库。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">docker</span> build <span class="token builtin class-name">.</span> <span class="token parameter variable">-t</span> guangzhengli/hellok8s:liveness
<span class="token function">docker</span> push guangzhengli/hellok8s:liveness<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<p>最后编写deployment的定义，这里使用存活探测方式是使用HTTP GET请求，请求的是刚才定义的<code>/healthz</code>接口，<code>periodSeconds</code>字段指定了kubelet每隔3秒执行一次存活探测。<code>initialDelaySeconds</code>字段告诉kubelet在执行第一次探测前应该等待3秒。如果服务器上<code>/healthz</code>路径下的处理程序返回成功代码，则kubelet认为容器是健康存活的。如果处理程序返回失败代码，则kubelet会杀死这个容器并将其重启。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>deployment
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">strategy</span><span class="token punctuation">:</span>
     <span class="token key atrule">rollingUpdate</span><span class="token punctuation">:</span>
      <span class="token key atrule">maxSurge</span><span class="token punctuation">:</span> <span class="token number">1</span>
      <span class="token key atrule">maxUnavailable</span><span class="token punctuation">:</span> <span class="token number">1</span>
  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">3</span>
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>
      <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
  <span class="token key atrule">template</span><span class="token punctuation">:</span>
    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>
      <span class="token key atrule">labels</span><span class="token punctuation">:</span>
        <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
    <span class="token key atrule">spec</span><span class="token punctuation">:</span>
      <span class="token key atrule">containers</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> guangzhengli/hellok8s<span class="token punctuation">:</span>liveness
          <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>container
          <span class="token key atrule">livenessProbe</span><span class="token punctuation">:</span>
            <span class="token key atrule">httpGet</span><span class="token punctuation">:</span>
              <span class="token key atrule">path</span><span class="token punctuation">:</span> /healthz
              <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3000</span>
            <span class="token key atrule">initialDelaySeconds</span><span class="token punctuation">:</span> <span class="token number">3</span>
            <span class="token key atrule">periodSeconds</span><span class="token punctuation">:</span> <span class="token number">3</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>通过<code>get</code>或者<code>describe</code>命令可以发现pod一直处于重启当中。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl apply <span class="token parameter variable">-f</span> deployment.yaml

kubectl get pods
<span class="token comment"># NAME                                   READY   STATUS    RESTARTS     AGE</span>
<span class="token comment"># hellok8s-deployment-5995ff9447-d5fbz   1/1     Running   4 (6s ago)   102s</span>
<span class="token comment"># hellok8s-deployment-5995ff9447-gz2cx   1/1     Running   4 (5s ago)   101s</span>
<span class="token comment"># hellok8s-deployment-5995ff9447-rh29x   1/1     Running   4 (6s ago)   102s</span>

kubectl describe pod hellok8s-68f47f657c-zwn6g

<span class="token comment"># ...</span>
<span class="token comment"># ...</span>
<span class="token comment"># ...</span>
<span class="token comment"># Events:</span>
<span class="token comment">#  Type     Reason     Age                   From               Message</span>
<span class="token comment">#  ----     ------     ----                  ----               -------</span>
<span class="token comment">#  Normal   Scheduled  12m                   default-scheduler  Successfully assigned default/hellok8s-deployment-5995ff9447-rh29x to minikube</span>
<span class="token comment">#  Normal   Pulled     11m (x4 over 12m)     kubelet            Container image "guangzhengli/hellok8s:liveness" already present on machine</span>
<span class="token comment">#  Normal   Created    11m (x4 over 12m)     kubelet            Created container hellok8s-container</span>
<span class="token comment">#  Normal   Started    11m (x4 over 12m)     kubelet            Started container hellok8s-container</span>
<span class="token comment">#  Normal   Killing    11m (x3 over 12m)     kubelet            Container hellok8s-container failed liveness probe, will be restarted</span>
<span class="token comment">#  Warning  Unhealthy  11m (x10 over 12m)    kubelet            Liveness probe failed: HTTP probe failed with statuscode: 500</span>
<span class="token comment">#  Warning  BackOff    2m41s (x36 over 10m)  kubelet            Back-off restarting failed container</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="就绪探针-readiness"><a href="#就绪探针-readiness" class="headerlink" title="就绪探针(readiness)"></a>就绪探针(readiness)</h3><blockquote>
<p>就绪探测器可以知道容器何时准备好接受请求流量，当一个Pod内的所有容器都就绪时，才能认为该Pod就绪。这种信号的一个用途就是控制哪个Pod作为Service的后端。若Pod尚未就绪，会被从Service的负载均衡器中剔除。–<a target="_blank" rel="noopener" href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/">ReadinessProb</a></p>
</blockquote>
<p>在生产环境中，升级服务的版本是日常的需求，这时我们需要考虑一种场景，即当发布的版本存在问题，就不应该让它升级成功。kubelet使用就绪探测器可以知道容器何时准备好接受请求流量，当一个pod升级后不能就绪，即不应该让流量进入该pod，在配合<code>rollingUpate</code>的功能下，也不能允许升级版本继续下去，否则服务会出现全部升级完成，导致所有服务均不可用的情况。这里我们把服务回滚到<code>hellok8s:v2</code>的版本，可以通过上面学习的方法进行回滚。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl rollout undo deployment hellok8s-deployment --to-revision<span class="token operator">=</span><span class="token number">2</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>这里我们将应用的<code>/healthz</code>接口直接设置成返回500状态码，代表该版本是一个有问题的版本。</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> main

<span class="token keyword">import</span> <span class="token punctuation">(</span>
	<span class="token string">"io"</span>
	<span class="token string">"net/http"</span>
<span class="token punctuation">)</span>

<span class="token keyword">func</span> <span class="token function">hello</span><span class="token punctuation">(</span>w http<span class="token punctuation">.</span>ResponseWriter<span class="token punctuation">,</span> r <span class="token operator">*</span>http<span class="token punctuation">.</span>Request<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	io<span class="token punctuation">.</span><span class="token function">WriteString</span><span class="token punctuation">(</span>w<span class="token punctuation">,</span> <span class="token string">"[v2] Hello, Kubernetes!"</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span>

<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	http<span class="token punctuation">.</span><span class="token function">HandleFunc</span><span class="token punctuation">(</span><span class="token string">"/healthz"</span><span class="token punctuation">,</span> <span class="token keyword">func</span><span class="token punctuation">(</span>w http<span class="token punctuation">.</span>ResponseWriter<span class="token punctuation">,</span> r <span class="token operator">*</span>http<span class="token punctuation">.</span>Request<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
			w<span class="token punctuation">.</span><span class="token function">WriteHeader</span><span class="token punctuation">(</span><span class="token number">500</span><span class="token punctuation">)</span>
	<span class="token punctuation">&#125;</span><span class="token punctuation">)</span>

	http<span class="token punctuation">.</span><span class="token function">HandleFunc</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">,</span> hello<span class="token punctuation">)</span>
	http<span class="token punctuation">.</span><span class="token function">ListenAndServe</span><span class="token punctuation">(</span><span class="token string">":3000"</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>在<code>build</code>阶段我们将<code>tag</code>设置为<code>bad</code>，打包后push到远程仓库。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">docker</span> build <span class="token builtin class-name">.</span> <span class="token parameter variable">-t</span> guangzhengli/hellok8s:bad

<span class="token function">docker</span> push guangzhengli/hellok8s:bad<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<p>接着编写deployment资源文件，<a target="_blank" rel="noopener" href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#probe-v1-core">Probe</a>有很多配置字段，可以使用这些字段精确地控制就绪检测的行为：</p>
<ul>
<li><code>initialDelaySeconds</code>：容器启动后要等待多少秒后才启动存活和就绪探测器，默认是0秒，最小值是0。</li>
<li><code>periodSeconds</code>：执行探测的时间间隔（单位是秒）。默认是10秒。最小值是1。</li>
<li><code>timeoutSeconds</code>：探测的超时后等待多少秒。默认值是1秒。最小值是1。</li>
<li><code>successThreshold</code>：探测器在失败后，被视为成功的最小连续成功数。默认值是1。存活和启动探测的这个值必须是1。最小值是1。</li>
<li><code>failureThreshold</code>：当探测失败时，Kubernetes的重试次数。对存活探测而言，放弃就意味着重新启动容器。对就绪探测而言，放弃意味着Pod会被打上未就绪的标签。默认值是3。最小值是1。</li>
</ul>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>deployment
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">strategy</span><span class="token punctuation">:</span>
     <span class="token key atrule">rollingUpdate</span><span class="token punctuation">:</span>
      <span class="token key atrule">maxSurge</span><span class="token punctuation">:</span> <span class="token number">1</span>
      <span class="token key atrule">maxUnavailable</span><span class="token punctuation">:</span> <span class="token number">1</span>
  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">3</span>
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>
      <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
  <span class="token key atrule">template</span><span class="token punctuation">:</span>
    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>
      <span class="token key atrule">labels</span><span class="token punctuation">:</span>
        <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
    <span class="token key atrule">spec</span><span class="token punctuation">:</span>
      <span class="token key atrule">containers</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> guangzhengli/hellok8s<span class="token punctuation">:</span>bad
          <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>container
          <span class="token key atrule">readinessProbe</span><span class="token punctuation">:</span>
            <span class="token key atrule">httpGet</span><span class="token punctuation">:</span>
              <span class="token key atrule">path</span><span class="token punctuation">:</span> /healthz
              <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3000</span>
            <span class="token key atrule">initialDelaySeconds</span><span class="token punctuation">:</span> <span class="token number">1</span>
            <span class="token key atrule">successThreshold</span><span class="token punctuation">:</span> <span class="token number">5</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>通过<code>get</code>命令可以发现两个pod一直处于还没有Ready的状态当中，通过<code>describe</code>命令可以看到是因为<code>Readiness probe failed: HTTP probe failed with statuscode: 500</code> 的原因。又因为设置了最小不可用的服务数量为<code>maxUnavailable=1</code>，这样能保证剩下两个<code>v2</code>版本的<code>hellok8s</code>能继续提供服务！</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl apply <span class="token parameter variable">-f</span> deployment.yaml

kubectl get pods                
<span class="token comment"># NAME                                   READY   STATUS    RESTARTS   AGE</span>
<span class="token comment"># hellok8s-deployment-66799848c4-8xzsz   1/1     Running   0          102s</span>
<span class="token comment"># hellok8s-deployment-66799848c4-m9dl5   1/1     Running   0          102s</span>
<span class="token comment"># hellok8s-deployment-9c57c7f56-rww7k    0/1     Running   0          26s</span>
<span class="token comment"># hellok8s-deployment-9c57c7f56-xt9tw    0/1     Running   0          26s</span>


kubectl describe pod hellok8s-deployment-9c57c7f56-rww7k
<span class="token comment"># Events:</span>
<span class="token comment">#   Type     Reason     Age                From               Message</span>
<span class="token comment">#   ----     ------     ----               ----               -------</span>
<span class="token comment">#   Normal   Scheduled  74s                default-scheduler  Successfully assigned default/hellok8s-deployment-9c57c7f56-rww7k to minikube</span>
<span class="token comment">#   Normal   Pulled     73s                kubelet            Container image "guangzhengli/hellok8s:bad" already present on machine</span>
<span class="token comment">#   Normal   Created    73s                kubelet            Created container hellok8s-container</span>
<span class="token comment">#   Normal   Started    73s                kubelet            Started container hellok8s-container</span>
<span class="token comment">#   Warning  Unhealthy  0s (x10 over 72s)  kubelet            Readiness probe failed: HTTP probe failed with statuscode: 500</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h2 id="Service"><a href="#Service" class="headerlink" title="Service"></a>Service</h2><p>经过前面几节的练习，可能你会有一些疑惑：</p>
<ul>
<li>为什么pod不就绪(Ready)的话，<code>kubernetes</code>不会将流量重定向到该pod，这是怎么做到的？</li>
<li>前面访问服务的方式是通过<code>port-forword</code>将pod的端口暴露到本地，不仅需要写对pod的名字，一旦deployment重新创建新的pod，pod名字和IP地址也会随之变化，如何保证稳定的访问地址呢？。</li>
<li>如果使用deployment部署了多个Pod副本，如何做负载均衡呢？</li>
</ul>
<p><code>kubernetes</code>提供了一种名叫<code>Service</code>的资源帮助解决这些问题，它为pod提供一个稳定的Endpoint。Service位于pod的前面，负责接收请求并将它们传递给它后面的所有pod。一旦服务中的Pod集合发生更改，Endpoints就会被更新，请求的重定向自然也会导向最新的pod。</p>
<h3 id="ClusterIP"><a href="#ClusterIP" class="headerlink" title="ClusterIP"></a>ClusterIP</h3><p>我们先来看看<code>Service</code>默认使用的<code>ClusterIP</code>类型，首先做一些准备工作，在之前的<code>hellok8s:v2</code>版本上加上返回当前服务所在的<code>hostname</code>功能，升级到<code>v3</code>版本。</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> main

<span class="token keyword">import</span> <span class="token punctuation">(</span>
	<span class="token string">"fmt"</span>
	<span class="token string">"io"</span>
	<span class="token string">"net/http"</span>
	<span class="token string">"os"</span>
<span class="token punctuation">)</span>

<span class="token keyword">func</span> <span class="token function">hello</span><span class="token punctuation">(</span>w http<span class="token punctuation">.</span>ResponseWriter<span class="token punctuation">,</span> r <span class="token operator">*</span>http<span class="token punctuation">.</span>Request<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	host<span class="token punctuation">,</span> <span class="token boolean">_</span> <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Hostname</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	io<span class="token punctuation">.</span><span class="token function">WriteString</span><span class="token punctuation">(</span>w<span class="token punctuation">,</span> fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">"[v3] Hello, Kubernetes!, From host: %s"</span><span class="token punctuation">,</span> host<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span>

<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	http<span class="token punctuation">.</span><span class="token function">HandleFunc</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">,</span> hello<span class="token punctuation">)</span>
	http<span class="token punctuation">.</span><span class="token function">ListenAndServe</span><span class="token punctuation">(</span><span class="token string">":3000"</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><code>Dockerfile</code>和之前保持一致，打包<code>tag=v3</code>并推送到远程仓库。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">docker</span> build <span class="token builtin class-name">.</span> <span class="token parameter variable">-t</span> guangzhengli/hellok8s:v3

<span class="token function">docker</span> push guangzhengli/hellok8s:v3<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<p>修改deployment的<code>hellok8s</code>为<code>v3</code>版本。执行<code>kubectl apply -f deployment.yaml</code>更新deployment。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>deployment
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">3</span>
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>
      <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
  <span class="token key atrule">template</span><span class="token punctuation">:</span>
    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>
      <span class="token key atrule">labels</span><span class="token punctuation">:</span>
        <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
    <span class="token key atrule">spec</span><span class="token punctuation">:</span>
      <span class="token key atrule">containers</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> guangzhengli/hellok8s<span class="token punctuation">:</span>v3
          <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>container<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>接下来是<code>Service</code>资源的定义，我们使用<code>ClusterIP</code>的方式定义Service，通过<code>kubernetes</code>集群的内部IP暴露服务，当我们只需要让集群中运行的其他应用程序访问我们的pod时，就可以使用这种类型的Service。首先创建一个service-hellok8s-clusterip.yaml文件。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Service
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> service<span class="token punctuation">-</span>hellok8s<span class="token punctuation">-</span>clusterip
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">type</span><span class="token punctuation">:</span> ClusterIP
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
  <span class="token key atrule">ports</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3000</span>
    <span class="token key atrule">targetPort</span><span class="token punctuation">:</span> <span class="token number">3000</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>首先通过<code>kubectl get endpoints</code>来看看Endpoint。被selector选中的Pod，就称为Service的Endpoints。它维护着Pod的IP地址，只要服务中的Pod集合发生更改，Endpoints就会被更新。通过<code>kubectl get pod -o wide</code>命令获取Pod更多的信息，可以看到3个Pod的IP地址和Endpoints中是保持一致的，你可以试试增大或减少Deployment中Pod的replicas，观察Endpoints会不会发生变化。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl apply <span class="token parameter variable">-f</span> service-hellok8s-clusterip.yaml

kubectl get endpoints
<span class="token comment"># NAME                         ENDPOINTS                                          AGE</span>
<span class="token comment"># service-hellok8s-clusterip   172.17.0.10:3000,172.17.0.2:3000,172.17.0.3:3000   10s</span>

kubectl get pod <span class="token parameter variable">-o</span> wide
<span class="token comment"># NAME                                   READY   STATUS    RESTARTS   AGE    IP           NODE </span>
<span class="token comment"># hellok8s-deployment-5d5545b69c-24lw5   1/1     Running   0          112s   172.17.0.7   minikube </span>
<span class="token comment"># hellok8s-deployment-5d5545b69c-9g94t   1/1     Running   0          112s   172.17.0.3   minikube</span>
<span class="token comment"># hellok8s-deployment-5d5545b69c-9gm8r   1/1     Running   0          112s   172.17.0.2   minikube</span>
<span class="token comment"># nginx                                  1/1     Running   0          112s   172.17.0.9   minikube</span>

kubectl get <span class="token function">service</span>
<span class="token comment"># NAME                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE</span>
<span class="token comment"># service-hellok8s-clusterip   ClusterIP   10.104.96.153   &lt;none>        3000/TCP   10s</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>接着我们可以通过在集群其它应用中访问<code>service-hellok8s-clusterip</code>的IP地址<code>10.104.96.153</code>来访问<code>hellok8s:v3</code>服务。这里通过在集群内创建一个<code>nginx</code>来访问<code>hellok8s</code>服务。创建后进入<code>nginx</code>容器来用<code>curl</code>命令访问<code>service-hellok8s-clusterip</code>。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Pod
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> nginx
  <span class="token key atrule">labels</span><span class="token punctuation">:</span>
    <span class="token key atrule">app</span><span class="token punctuation">:</span> nginx
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">containers</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> nginx<span class="token punctuation">-</span>container
      <span class="token key atrule">image</span><span class="token punctuation">:</span> nginx<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl get pods
<span class="token comment"># NAME                                   READY   STATUS    RESTARTS   AGE</span>
<span class="token comment"># hellok8s-deployment-5d5545b69c-24lw5   1/1     Running   0          27m</span>
<span class="token comment"># hellok8s-deployment-5d5545b69c-9g94t   1/1     Running   0          27m</span>
<span class="token comment"># hellok8s-deployment-5d5545b69c-9gm8r   1/1     Running   0          27m</span>
<span class="token comment"># nginx                                  1/1     Running   0          41m</span>

kubectl get <span class="token function">service</span>
<span class="token comment"># NAME                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE</span>
<span class="token comment"># service-hellok8s-clusterip   ClusterIP   10.104.96.153   &lt;none>        3000/TCP   10s</span>

kubectl <span class="token builtin class-name">exec</span> <span class="token parameter variable">-it</span> nginx-pod /bin/bash
<span class="token comment"># root@nginx-pod:/# curl 10.104.96.153:3000</span>
<span class="token comment"># [v3] Hello, Kubernetes!, From host: hellok8s-deployment-5d5545b69c-9gm8r</span>
<span class="token comment"># root@nginx-pod:/# curl 10.104.96.153:3000</span>
<span class="token comment">#[v3] Hello, Kubernetes!, From host: hellok8s-deployment-5d5545b69c-9g94t</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>可以看到，我们多次<code>curl 10.104.96.153:3000</code>访问<code>hellok8s</code>Service IP地址，返回的<code>hellok8s:v3</code> <code>hostname</code>不一样，说明Service可以接收请求并将它们传递给它后面的所有pod，还可以自动负载均衡。你也可以试试增加或者减少<code>hellok8s:v3</code>pod副本数量，观察Service的请求是否会动态变更。调用过程如下图所示：</p>
<p><img src="https://cdn.jsdelivr.net/gh/guangzhengli/PicURL@master/uPic/service-clusterip-fix-name.png" alt="service-clusterip-fix-name"></p>
<p>除了上述的<code>ClusterIp</code>的方式外，Kubernetes<code>ServiceTypes</code>允许指定你所需要的Service类型，默认是<code>ClusterIP</code>。<code>Type</code>的值包括如下：</p>
<ul>
<li><code>ClusterIP</code>：通过集群的内部IP暴露服务，选择该值时服务只能够在集群内部访问。这也是默认的<code>ServiceType</code>。</li>
<li><a target="_blank" rel="noopener" href="https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport"><code>NodePort</code></a>：通过每个节点上的IP和静态端口（<code>NodePort</code>）暴露服务。<code>NodePort</code>服务会路由到自动创建的<code>ClusterIP</code>服务。通过请求<code>&lt;节点IP&gt;:&lt;节点端口&gt;</code>，你可以从集群的外部访问一个<code>NodePort</code>服务。</li>
<li><a target="_blank" rel="noopener" href="https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer"><code>LoadBalancer</code></a>：使用云提供商的负载均衡器向外部暴露服务。外部负载均衡器可以将流量路由到自动创建的<code>NodePort</code>服务和<code>ClusterIP</code>服务上。</li>
<li><a target="_blank" rel="noopener" href="https://kubernetes.io/docs/concepts/services-networking/service/#externalname"><code>ExternalName</code></a>：通过返回<code>CNAME</code>和对应值，可以将服务映射到<code>externalName</code>字段的内容（例如，<code>foo.bar.example.com</code>）。无需创建任何类型代理。</li>
</ul>
<h3 id="NodePort"><a href="#NodePort" class="headerlink" title="NodePort"></a>NodePort</h3><p>我们知道<code>kubernetes</code>集群并不是单机运行，它管理着多台节点即<a target="_blank" rel="noopener" href="https://kubernetes.io/docs/concepts/architecture/nodes/">Node</a>，可以通过每个节点上的IP和静态端口（<code>NodePort</code>）暴露服务。如下图所示，如果集群内有两台Node运行着<code>hellok8s:v3</code>，我们创建一个<code>NodePort</code>类型的Service，将<code>hellok8s:v3</code>的<code>3000</code>端口映射到Node机器的<code>30000</code>端口(在30000-32767范围内)，就可以通过访问<code>http://node1-ip:30000</code>或者<code>http://node2-ip:30000</code>访问到服务。</p>
<p><img src="https://cdn.jsdelivr.net/gh/guangzhengli/PicURL@master/uPic/service-nodeport-fix-name.png" alt="service-nodeport-fix-name"></p>
<p>这里以<code>minikube</code>为例，我们可以通过<code>minikube ip</code>命令拿到k8s cluster node IP地址。下面的教程都以我本机的<code>192.168.59.100</code>为例，需要替换成你的IP地址。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">minikube <span class="token function">ip</span>
<span class="token comment"># 192.168.59.100</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span></span></code></pre>

<p>接着以NodePort的ServiceType创建一个Service来接管pod流量。通过<code>minikube</code>节点上的IP<code>192.168.59.100</code>暴露服务。<code>NodePort</code>服务会路由到自动创建的<code>ClusterIP</code>服务。通过请求<code>&lt;节点 IP&gt;:&lt;节点端口&gt;</code> – <code>192.168.59.100</code>:30000，你可以从集群的外部访问一个<code>NodePort</code>服务，最终重定向到<code>hellok8s:v3</code>的<code>3000</code>端口。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Service
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> service<span class="token punctuation">-</span>hellok8s<span class="token punctuation">-</span>nodeport
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">type</span><span class="token punctuation">:</span> NodePort
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
  <span class="token key atrule">ports</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3000</span>
    <span class="token key atrule">nodePort</span><span class="token punctuation">:</span> <span class="token number">30000</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>创建<code>service-hellok8s-nodeport</code>Service后，使用<code>curl</code>命令或者浏览器访问<code>http://192.168.59.100:30000</code>可以得到结果。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl apply <span class="token parameter variable">-f</span> service-hellok8s-nodeport.yaml

kubectl get <span class="token function">service</span>
<span class="token comment"># NAME                         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE</span>
<span class="token comment"># service-hellok8s-nodeport    NodePort    10.109.188.161   &lt;none>        3000:30000/TCP   28s</span>

kubectl get pods
<span class="token comment"># NAME                                   READY   STATUS    RESTARTS   AGE</span>
<span class="token comment"># hellok8s-deployment-5d5545b69c-24lw5   1/1     Running   0          27m</span>
<span class="token comment"># hellok8s-deployment-5d5545b69c-9g94t   1/1     Running   0          27m</span>
<span class="token comment"># hellok8s-deployment-5d5545b69c-9gm8r   1/1     Running   0          27m</span>

<span class="token function">curl</span> http://192.168.59.100:30000
<span class="token comment"># [v3] Hello, Kubernetes!, From host: hellok8s-deployment-5d5545b69c-9g94t</span>

<span class="token function">curl</span> http://192.168.59.100:30000
<span class="token comment"># [v3] Hello, Kubernetes!, From host: hellok8s-deployment-5d5545b69c-24lw5</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="LoadBalancer"><a href="#LoadBalancer" class="headerlink" title="LoadBalancer"></a>LoadBalancer</h3><p><a target="_blank" rel="noopener" href="https://kubernetes.io/docs/concepts/services-networking/service/#loadbalancer"><code>LoadBalancer</code></a>是使用云提供商的负载均衡器向外部暴露服务。外部负载均衡器可以将流量路由到自动创建的<code>NodePort</code>服务和<code>ClusterIP</code>服务上，假如你在<a target="_blank" rel="noopener" href="https://aws.amazon.com/">AWS</a>的<a target="_blank" rel="noopener" href="https://aws.amazon.com/eks/">EKS</a>集群上创建一个Type为<code>LoadBalancer</code>的Service。它会自动创建一个ELB(<a target="_blank" rel="noopener" href="https://aws.amazon.com/elasticloadbalancing">Elastic Load Balancer</a>)，并可以根据配置的IP池中自动分配一个独立的IP地址，可以供外部访问。这里因为我们使用的是<code>minikube</code>，可以使用<code>minikube tunnel</code>来辅助创建LoadBalancer的<code>EXTERNAL_IP</code>，具体教程可以查看<a target="_blank" rel="noopener" href="https://minikube.sigs.k8s.io/docs/handbook/accessing/#loadbalancer-access">官网文档</a>，但是和实际云提供商的LoadBalancer还是有本质区别，所以<a target="_blank" rel="noopener" href="https://github.com/guangzhengli/kubernetes_workshop">Repository</a>不做更多阐述，有条件的可以使用<a target="_blank" rel="noopener" href="https://aws.amazon.com/">AWS</a>的<a target="_blank" rel="noopener" href="https://aws.amazon.com/eks/">EKS</a>集群上创建一个ELB(<a target="_blank" rel="noopener" href="https://aws.amazon.com/elasticloadbalancing">Elastic Load Balancer</a>)试试。下图显示LoadBalancer的Service架构图。</p>
<p><img src="https://cdn.jsdelivr.net/gh/guangzhengli/PicURL@master/uPic/service-loadbalancer-fix-name.png" alt="service-loadbalancer-fix-name"></p>
<h2 id="ingress"><a href="#ingress" class="headerlink" title="ingress"></a>ingress</h2><p><a target="_blank" rel="noopener" href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#ingress-v1beta1-networking-k8s-io">Ingress</a>公开从集群外部到集群内<a target="_blank" rel="noopener" href="https://kubernetes.io/docs/concepts/services-networking/service/">服务</a>的HTTP和HTTPS路由。流量路由由Ingress资源上定义的规则控制。Ingress可为Service提供外部可访问的URL、负载均衡流量、SSL&#x2F;TLS，以及基于名称的虚拟托管。你必须拥有一个<a target="_blank" rel="noopener" href="https://kubernetes.io/zh-cn/docs/concepts/services-networking/ingress-controllers">Ingress控制器</a>才能满足Ingress的要求。仅创建Ingress资源本身没有任何效果。<a target="_blank" rel="noopener" href="https://kubernetes.io/docs/concepts/services-networking/ingress-controllers">Ingress控制器</a>通常负责通过负载均衡器来实现Ingress，例如<code>minikube</code>默认使用的是<a target="_blank" rel="noopener" href="https://minikube.sigs.k8s.io/docs/tutorials/nginx_tcp_udp_ingress/">nginx-ingress</a>，目前<code>minikube</code>也支持<a target="_blank" rel="noopener" href="https://minikube.sigs.k8s.io/docs/handbook/addons/kong-ingress/">Kong-Ingress</a>。Ingress可以“简单理解”为服务的网关Gateway，它是所有流量的入口，经过配置的路由规则，将流量重定向到后端的服务在<code>minikube</code>中，可以通过下面命令开启Ingress-Controller的功能。默认使用的是<a target="_blank" rel="noopener" href="https://minikube.sigs.k8s.io/docs/tutorials/nginx_tcp_udp_ingress/">nginx-ingress</a>。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">minikube addons <span class="token builtin class-name">enable</span> ingress<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>接着删除之前创建的所有<code>pod</code>,<code>deployment</code>,<code>service</code>资源。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl delete deployment,service <span class="token parameter variable">--all</span><span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>接着根据之前的教程，创建<code>hellok8s:v3</code>和<code>nginx</code>的<code>deployment</code>与<code>service</code>资源。Service的type为Cluster IP即可。</p>
<p><code>hellok8s:v3</code>的端口映射为<code>3000:3000</code>，<code>nginx</code>的端口映射为<code>4000:80</code>，这里后续写Ingress Route 规则时会用到。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Service
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> service<span class="token punctuation">-</span>hellok8s<span class="token punctuation">-</span>clusterip
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">type</span><span class="token punctuation">:</span> ClusterIP
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
  <span class="token key atrule">ports</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">3000</span>
    <span class="token key atrule">targetPort</span><span class="token punctuation">:</span> <span class="token number">3000</span>

<span class="token punctuation">---</span>

<span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>deployment
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">3</span>
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>
      <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
  <span class="token key atrule">template</span><span class="token punctuation">:</span>
    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>
      <span class="token key atrule">labels</span><span class="token punctuation">:</span>
        <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
    <span class="token key atrule">spec</span><span class="token punctuation">:</span>
      <span class="token key atrule">containers</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> guangzhengli/hellok8s<span class="token punctuation">:</span>v3
          <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>container<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Service
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> service<span class="token punctuation">-</span>nginx<span class="token punctuation">-</span>clusterip
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">type</span><span class="token punctuation">:</span> ClusterIP
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">app</span><span class="token punctuation">:</span> nginx
  <span class="token key atrule">ports</span><span class="token punctuation">:</span>
  <span class="token punctuation">-</span> <span class="token key atrule">port</span><span class="token punctuation">:</span> <span class="token number">4000</span>
    <span class="token key atrule">targetPort</span><span class="token punctuation">:</span> <span class="token number">80</span>

<span class="token punctuation">---</span>

<span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> nginx<span class="token punctuation">-</span>deployment
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">2</span>
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>
      <span class="token key atrule">app</span><span class="token punctuation">:</span> nginx
  <span class="token key atrule">template</span><span class="token punctuation">:</span>
    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>
      <span class="token key atrule">labels</span><span class="token punctuation">:</span>
        <span class="token key atrule">app</span><span class="token punctuation">:</span> nginx
    <span class="token key atrule">spec</span><span class="token punctuation">:</span>
      <span class="token key atrule">containers</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> nginx
        <span class="token key atrule">name</span><span class="token punctuation">:</span> nginx<span class="token punctuation">-</span>container<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl apply <span class="token parameter variable">-f</span> hellok8s.yaml
<span class="token comment"># service/service-hellok8s-clusterip created</span>
<span class="token comment"># deployment.apps/hellok8s-deployment created</span>

kubectl apply <span class="token parameter variable">-f</span> nginx.yaml   
<span class="token comment"># service/service-nginx-clusterip created</span>
<span class="token comment"># deployment.apps/nginx-deployment created</span>

kubectl get pods            
<span class="token comment"># NAME                                   READY   STATUS    RESTARTS   AGE</span>
<span class="token comment"># hellok8s-deployment-5d5545b69c-4wvmf   1/1     Running   0          55s</span>
<span class="token comment"># hellok8s-deployment-5d5545b69c-qcszp   1/1     Running   0          55s</span>
<span class="token comment"># hellok8s-deployment-5d5545b69c-sn7mn   1/1     Running   0          55s</span>
<span class="token comment"># nginx-deployment-d47fd7f66-d9r7x       1/1     Running   0          34s</span>
<span class="token comment"># nginx-deployment-d47fd7f66-hp5nf       1/1     Running   0          34s</span>

kubectl get <span class="token function">service</span>
<span class="token comment"># NAME                         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE</span>
<span class="token comment"># service-hellok8s-clusterip   ClusterIP   10.97.88.18      &lt;none>        3000/TCP   77s</span>
<span class="token comment"># service-nginx-clusterip      ClusterIP   10.103.161.247   &lt;none>        4000/TCP   56s</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>这样在k8s集群中，就有3个<code>hellok8s:v3</code>的pod，2个<code>nginx</code>的pod。并且<code>hellok8s:v3</code>的端口映射为<code>3000:3000</code>，<code>nginx</code>的端口映射为<code>4000:80</code>。在这个基础上，接下来编写Ingress资源的定义，<code>nginx.ingress.kubernetes.io/ssl-redirect:&quot;false&quot;</code>的意思是这里关闭<code>https</code>连接，只使用<code>http</code>连接。匹配前缀为<code>/hello</code>的路由规则，重定向到<code>hellok8s:v3</code>服务，匹配前缀为<code>/</code>的跟路径重定向到<code>nginx</code>。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> networking.k8s.io/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Ingress
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hello<span class="token punctuation">-</span>ingress
  <span class="token key atrule">annotations</span><span class="token punctuation">:</span>
    <span class="token comment"># We are defining this annotation to prevent nginx</span>
    <span class="token comment"># from redirecting requests to `https` for now</span>
    <span class="token key atrule">nginx.ingress.kubernetes.io/ssl-redirect</span><span class="token punctuation">:</span> <span class="token string">"false"</span>
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">rules</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token key atrule">http</span><span class="token punctuation">:</span>
        <span class="token key atrule">paths</span><span class="token punctuation">:</span>
          <span class="token punctuation">-</span> <span class="token key atrule">path</span><span class="token punctuation">:</span> /hello
            <span class="token key atrule">pathType</span><span class="token punctuation">:</span> Prefix
            <span class="token key atrule">backend</span><span class="token punctuation">:</span>
              <span class="token key atrule">service</span><span class="token punctuation">:</span>
                <span class="token key atrule">name</span><span class="token punctuation">:</span> service<span class="token punctuation">-</span>hellok8s<span class="token punctuation">-</span>clusterip
                <span class="token key atrule">port</span><span class="token punctuation">:</span>
                  <span class="token key atrule">number</span><span class="token punctuation">:</span> <span class="token number">3000</span>
          <span class="token punctuation">-</span> <span class="token key atrule">path</span><span class="token punctuation">:</span> /
            <span class="token key atrule">pathType</span><span class="token punctuation">:</span> Prefix
            <span class="token key atrule">backend</span><span class="token punctuation">:</span>
              <span class="token key atrule">service</span><span class="token punctuation">:</span>
                <span class="token key atrule">name</span><span class="token punctuation">:</span> service<span class="token punctuation">-</span>nginx<span class="token punctuation">-</span>clusterip
                <span class="token key atrule">port</span><span class="token punctuation">:</span>
                  <span class="token key atrule">number</span><span class="token punctuation">:</span> <span class="token number">4000</span>
<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl apply <span class="token parameter variable">-f</span> ingress.yaml
<span class="token comment"># ingress.extensions/hello-ingress created</span>

kubectl get ingress          
<span class="token comment"># NAME            CLASS   HOSTS   ADDRESS   PORTS   AGE</span>
<span class="token comment"># hello-ingress   nginx   *                 80      16s</span>

<span class="token comment"># replace 192.168.59.100 by your minikube ip</span>
<span class="token function">curl</span> http://192.168.59.100/hello
<span class="token comment"># [v3] Hello, Kubernetes!, From host: hellok8s-deployment-5d5545b69c-sn7mn</span>

<span class="token function">curl</span> http://192.168.59.100/
<span class="token comment"># (....Thank you for using nginx.....)</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>上面的教程中将所有流量都发送到Ingress中，如下图所示：</p>
<p><img src="https://cdn.jsdelivr.net/gh/guangzhengli/PicURL@master/uPic/ingress.png" alt="ingress"></p>
<h2 id="Namespace"><a href="#Namespace" class="headerlink" title="Namespace"></a>Namespace</h2><p>在实际的开发当中，有时候我们需要不同的环境来做开发和测试，例如<code>dev</code>环境给开发使用，<code>test</code>环境给QA使用，那么k8s能不能在不同环境<code>dev</code> <code>test</code> <code>uat</code> <code>prod</code> 中区分资源，让不同环境的资源独立互相不影响呢，答案是肯定的，k8s提供了名为Namespace的资源来帮助隔离资源。在Kubernetes中，<strong>名字空间（Namespace）</strong>提供一种机制，将同一集群中的资源划分为相互隔离的组。同一名字空间内的资源名称要唯一，但跨名字空间时没有这个要求。名字空间作用域仅针对带有名字空间的对象，例如Deployment、Service等。前面的教程中，默认使用的namespace是<code>default</code>。下面展示如何创建一个新的namespace，<code>namespace.yaml</code>文件定义了两个不同的namespace，分别是<code>dev</code>和<code>test</code>。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Namespace
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> dev
  
<span class="token punctuation">---</span>

<span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Namespace
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> test<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>可以通过<code>kubectl apply -f namespaces.yaml</code>创建两个新的namespace，分别是<code>dev</code>和<code>test</code>。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml">kubectl apply <span class="token punctuation">-</span>f namespaces.yaml
<span class="token comment"># namespace/dev created</span>
<span class="token comment"># namespace/test created</span>


kubectl get namespaces
<span class="token comment"># NAME              STATUS   AGE</span>
<span class="token comment"># default           Active   215d</span>
<span class="token comment"># dev               Active   2m44s</span>
<span class="token comment"># ingress-nginx     Active   110d</span>
<span class="token comment"># kube-node-lease   Active   215d</span>
<span class="token comment"># kube-public       Active   215d</span>
<span class="token comment"># kube-system       Active   215d</span>
<span class="token comment"># test              Active   2m44s</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>那么如何在新的namespace下创建资源和获取资源呢？只需要在命令后面加上<code>-n namespace</code>即可。例如根据上面教程中，在名为<code>dev</code>的namespace下创建<code>hellok8s:v3</code>的deployment资源。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl apply <span class="token parameter variable">-f</span> deployment.yaml <span class="token parameter variable">-n</span> dev

kubectl get pods <span class="token parameter variable">-n</span> dev<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span></span></code></pre>

<h2 id="Configmap"><a href="#Configmap" class="headerlink" title="Configmap"></a>Configmap</h2><p>上面的教程提到，我们在不同环境<code>dev</code> <code>test</code> <code>uat</code> <code>prod</code>中区分资源，可以让其资源独立互相不受影响，但是随之而来也会带来一些问题，例如不同环境的数据库的地址往往是不一样的，那么如果在代码中写同一个数据库的地址，就会出现问题。K8S使用ConfigMap来将你的配置数据和应用程序代码分开，将非机密性的数据保存到键值对中。ConfigMap在设计上不是用来保存大量数据的。在ConfigMap中保存的数据不可超过1MiB。如果你需要保存超出此尺寸限制的数据，你可能考虑挂载存储卷。下面我们可以来看一个例子，我们修改之前代码，假设不同环境的数据库地址不同，下面代码从环境变量中获取<code>DB_URL</code>，并将它返回。</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> main

<span class="token keyword">import</span> <span class="token punctuation">(</span>
	<span class="token string">"fmt"</span>
	<span class="token string">"io"</span>
	<span class="token string">"net/http"</span>
	<span class="token string">"os"</span>
<span class="token punctuation">)</span>

<span class="token keyword">func</span> <span class="token function">hello</span><span class="token punctuation">(</span>w http<span class="token punctuation">.</span>ResponseWriter<span class="token punctuation">,</span> r <span class="token operator">*</span>http<span class="token punctuation">.</span>Request<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	host<span class="token punctuation">,</span> <span class="token boolean">_</span> <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Hostname</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	dbURL <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Getenv</span><span class="token punctuation">(</span><span class="token string">"DB_URL"</span><span class="token punctuation">)</span>
	io<span class="token punctuation">.</span><span class="token function">WriteString</span><span class="token punctuation">(</span>w<span class="token punctuation">,</span> fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">"[v4] Hello, Kubernetes! From host: %s, Get Database Connect URL: %s"</span><span class="token punctuation">,</span> host<span class="token punctuation">,</span> dbURL<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span>

<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	http<span class="token punctuation">.</span><span class="token function">HandleFunc</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">,</span> hello<span class="token punctuation">)</span>
	http<span class="token punctuation">.</span><span class="token function">ListenAndServe</span><span class="token punctuation">(</span><span class="token string">":3000"</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>构建<code>hellok8s:v4</code>的镜像，推送到远程仓库。并删除之前创建的所有资源。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">docker</span> build <span class="token builtin class-name">.</span> <span class="token parameter variable">-t</span> guangzhengli/hellok8s:v4
<span class="token function">docker</span> push guangzhengli/hellok8s:v4

kubectl delete deployment,service,ingress <span class="token parameter variable">--all</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span></span></code></pre>

<p>接下来创建不同namespace的configmap来存放<code>DB_URL</code>。</p>
<p>创建<code>hellok8s-config-dev.yaml</code>文件</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> ConfigMap
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>config
<span class="token key atrule">data</span><span class="token punctuation">:</span>
  <span class="token key atrule">DB_URL</span><span class="token punctuation">:</span> <span class="token string">"http://DB_ADDRESS_DEV"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>创建<code>hellok8s-config-test.yaml</code>文件</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> ConfigMap
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>config
<span class="token key atrule">data</span><span class="token punctuation">:</span>
  <span class="token key atrule">DB_URL</span><span class="token punctuation">:</span> <span class="token string">"http://DB_ADDRESS_TEST"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>分别在<code>dev</code> <code>test</code>两个namespace下创建相同的<code>ConfigMap</code>，名字都叫hellok8s-config，但是存放的Pair对中Value值不一样。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl apply <span class="token parameter variable">-f</span> hellok8s-config-dev.yaml <span class="token parameter variable">-n</span> dev
<span class="token comment"># configmap/hellok8s-config created</span>

kubectl apply <span class="token parameter variable">-f</span> hellok8s-config-test.yaml <span class="token parameter variable">-n</span> <span class="token builtin class-name">test</span> 
<span class="token comment"># configmap/hellok8s-config created</span>

kubectl get configmap --all-namespaces
NAMESPACE         NAME                                 DATA   AGE
dev               hellok8s-config                      <span class="token number">1</span>      3m12s
<span class="token builtin class-name">test</span>              hellok8s-config                      <span class="token number">1</span>      2m1s<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>接着使用POD的方式来部署<code>hellok8s:v4</code>，其中<code>env.name</code>表示的是将configmap中的值写进环境变量，这样代码从环境变量中获取<code>DB_URL</code>，这个KEY名称必须保持一致。<code>valueFrom</code>代表从哪里读取，<code>configMapKeyRef</code>这里表示从名为<code>hellok8s-config</code>的<code>configMap</code>中读取<code>KEY=DB_URL</code>的Value。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Pod
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>pod
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">containers</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>container
      <span class="token key atrule">image</span><span class="token punctuation">:</span> guangzhengli/hellok8s<span class="token punctuation">:</span>v4
      <span class="token key atrule">env</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> DB_URL
          <span class="token key atrule">valueFrom</span><span class="token punctuation">:</span>
            <span class="token key atrule">configMapKeyRef</span><span class="token punctuation">:</span>
              <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>config
              <span class="token key atrule">key</span><span class="token punctuation">:</span> DB_URL<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>下面分别在<code>dev</code> <code>test</code>两个namespace下创建<code>hellok8s:v4</code>，接着通过<code>port-forward</code>的方式访问不同namespace的服务，可以看到返回的<code>Get Database Connect URL: http://DB_ADDRESS_TEST</code>是不一样的！</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl apply <span class="token parameter variable">-f</span> hellok8s.yaml <span class="token parameter variable">-n</span> dev
<span class="token comment"># pod/hellok8s-pod created</span>

kubectl apply <span class="token parameter variable">-f</span> hellok8s.yaml <span class="token parameter variable">-n</span> <span class="token builtin class-name">test</span>
<span class="token comment"># pod/hellok8s-pod created</span>

kubectl port-forward hellok8s-pod <span class="token number">3000</span>:3000 <span class="token parameter variable">-n</span> dev

<span class="token function">curl</span> http://localhost:3000
<span class="token comment"># [v4] Hello, Kubernetes! From host: hellok8s-pod, Get Database Connect URL: http://DB_ADDRESS_DEV</span>

kubectl port-forward hellok8s-pod <span class="token number">3000</span>:3000 <span class="token parameter variable">-n</span> <span class="token builtin class-name">test</span>

<span class="token function">curl</span> http://localhost:3000
<span class="token comment"># [v4] Hello, Kubernetes! From host: hellok8s-pod, Get Database Connect URL: http://DB_ADDRESS_TEST</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h2 id="Secret"><a href="#Secret" class="headerlink" title="Secret"></a>Secret</h2><p>上面提到，我们会选择以configmap的方式挂载配置信息，但是当我们的配置信息需要加密的时候，configmap就无法满足这个要求。例如上面要挂载数据库密码的时候，就需要明文挂载。</p>
<p>这个时候就需要Secret来存储加密信息，虽然在资源文件的编码上，只是通过Base64的方式简单编码，但是在实际生产过程中，可以通过pipeline或者专业的<a target="_blank" rel="noopener" href="https://aws.amazon.com/kms/">AWS KMS</a>服务进行密钥管理。这样就大大减少了安全事故。</p>
<blockquote>
<p>Secret是一种包含少量敏感信息例如密码、令牌或密钥的对象。由于创建Secret可以独立于使用它们的Pod，因此在创建、查看和编辑Pod的工作流程中暴露Secret（及其数据）的风险较小。Kubernetes和在集群中运行的应用程序也可以对Secret采取额外的预防措施，例如避免将机密数据写入非易失性存储。</p>
<p>默认情况下，Kubernetes Secret未加密地存储在API服务器的底层数据存储（etcd）中。任何拥有API访问权限的人都可以检索或修改Secret，任何有权访问etcd的人也可以。此外，任何有权限在命名空间中创建Pod的人都可以使用该访问权限读取该命名空间中的任何Secret；这包括间接访问，例如创建Deployment的能力。</p>
<p>为了安全地使用Secret，请至少执行以下步骤：</p>
<ol>
<li>为Secret<a target="_blank" rel="noopener" href="https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/">启用静态加密</a>；</li>
<li><a target="_blank" rel="noopener" href="https://kubernetes.io/docs/reference/access-authn-authz/authorization/">启用或配置RBAC规则</a>来限制读取和写入Secret的数据（包括通过间接方式）。需要注意的是，被准许创建Pod的人也隐式地被授权获取Secret内容。</li>
<li>在适当的情况下，还可以使用RBAC等机制来限制允许哪些主体创建新Secret或替换现有Secret。</li>
</ol>
</blockquote>
<p>Secret的资源定义和ConfigMap结构基本一致，唯一区别在于kind是<code>Secret</code>，还有Value需要Base64编码，你可以通过下面命令快速Base64编解码。当然Secret也提供了一种<code>stringData</code>，可以不需要Base64编码。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token builtin class-name">echo</span> <span class="token string">"db_password"</span> <span class="token operator">|</span> base64
<span class="token comment"># ZGJfcGFzc3dvcmQK</span>

<span class="token builtin class-name">echo</span> <span class="token string">"ZGJfcGFzc3dvcmQK"</span> <span class="token operator">|</span> base64 <span class="token parameter variable">-d</span>
<span class="token comment"># db_password</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>这里将Base64编码过后的值，填入对应的key-value中。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token comment"># hellok8s-secret.yaml</span>
<span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Secret
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>secret
<span class="token key atrule">data</span><span class="token punctuation">:</span>
  <span class="token key atrule">DB_PASSWORD</span><span class="token punctuation">:</span> <span class="token string">"ZGJfcGFzc3dvcmQK"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token comment"># hellok8s.yaml</span>
<span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Pod
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>pod
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">containers</span><span class="token punctuation">:</span>
    <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>container
      <span class="token key atrule">image</span><span class="token punctuation">:</span> guangzhengli/hellok8s<span class="token punctuation">:</span>v5
      <span class="token key atrule">env</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> DB_PASSWORD
          <span class="token key atrule">valueFrom</span><span class="token punctuation">:</span>
            <span class="token key atrule">secretKeyRef</span><span class="token punctuation">:</span>
              <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>secret
              <span class="token key atrule">key</span><span class="token punctuation">:</span> DB_PASSWORD<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> main

<span class="token keyword">import</span> <span class="token punctuation">(</span>
	<span class="token string">"fmt"</span>
	<span class="token string">"io"</span>
	<span class="token string">"net/http"</span>
	<span class="token string">"os"</span>
<span class="token punctuation">)</span>

<span class="token keyword">func</span> <span class="token function">hello</span><span class="token punctuation">(</span>w http<span class="token punctuation">.</span>ResponseWriter<span class="token punctuation">,</span> r <span class="token operator">*</span>http<span class="token punctuation">.</span>Request<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	host<span class="token punctuation">,</span> <span class="token boolean">_</span> <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Hostname</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	dbPassword <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Getenv</span><span class="token punctuation">(</span><span class="token string">"DB_PASSWORD"</span><span class="token punctuation">)</span>
	io<span class="token punctuation">.</span><span class="token function">WriteString</span><span class="token punctuation">(</span>w<span class="token punctuation">,</span> fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">"[v5] Hello, Kubernetes! From host: %s, Get Database Connect Password: %s"</span><span class="token punctuation">,</span> host<span class="token punctuation">,</span> dbPassword<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span>

<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	http<span class="token punctuation">.</span><span class="token function">HandleFunc</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">,</span> hello<span class="token punctuation">)</span>
	http<span class="token punctuation">.</span><span class="token function">ListenAndServe</span><span class="token punctuation">(</span><span class="token string">":3000"</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>在代码中读取<code>DB_PASSWORD</code>环境变量，直接返回对应字符串。Secret的使用方法和前面教程中ConfigMap基本一致，这里就不再过多赘述。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token function">docker</span> build <span class="token builtin class-name">.</span> <span class="token parameter variable">-t</span> guangzhengli/hellok8s:v5

<span class="token function">docker</span> push guangzhengli/hellok8s:v5

kubectl apply <span class="token parameter variable">-f</span> hellok8s-secret.yaml

kubectl apply <span class="token parameter variable">-f</span> hellok8s.yaml

kubectl port-forward hellok8s-pod <span class="token number">3000</span>:3000<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>


<h2 id="Job"><a href="#Job" class="headerlink" title="Job"></a>Job</h2><p>在实际的开发过程中，还有一类任务是之前的资源不能满足的，即一次性任务。例如常见的计算任务，只需要拿到相关数据计算后得出结果即可，无需一直运行。而处理这一类任务的资源就是Job。</p>
<blockquote>
<p>Job会创建一个或者多个Pod，并将继续重试Pod的执行，直到指定数量的Pod成功终止。随着Pod成功结束，Job跟踪记录成功完成的Pod个数。当数量达到指定的成功个数阈值时，任务（即Job）结束。删除Job的操作会清除所创建的全部Pod。挂起Job的操作会删除Job的所有活跃Pod，直到Job被再次恢复执行。</p>
<p>一种简单的使用场景下，你会创建一个Job对象以便以一种可靠的方式运行某Pod直到完成。当第一个Pod失败或者被删除（比如因为节点硬件失效或者重启）时，Job对象会启动一个新的Pod。</p>
</blockquote>
<p>下面来看一个Job的资源定义，其中Kind和metadata.name是资源类型和名字就不再解释，<code>completions</code>指的是会创建Pod的数量，每个pod都会完成下面的任务。<code>parallelism</code>指的是并发执行最大数量，例如下面就会先创建3个pod并发执行任务，一旦某个pod执行完成，就会再创建新的pod来执行，直到5个pod执行完成，Job才会被标记为完成。</p>
<p><code>restartPolicy=&quot;OnFailure</code>的含义和Pod生命周期相关，Pod中的容器可能因为退出时返回值非零，或者容器因为超出内存约束而被杀死等等。如果发生这类事件，并且<code>.spec.template.spec.restartPolicy=&quot;OnFailure&quot;</code>，Pod则继续留在当前节点，但容器会被重新运行。因此，你的程序需要能够处理在本地被重启的情况，或者要设置<code>.spec.template.spec.restartPolicy=&quot;Never&quot;</code>。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> batch/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Job
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hello<span class="token punctuation">-</span>job
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">parallelism</span><span class="token punctuation">:</span> <span class="token number">3</span>
  <span class="token key atrule">completions</span><span class="token punctuation">:</span> <span class="token number">5</span>
  <span class="token key atrule">template</span><span class="token punctuation">:</span>
    <span class="token key atrule">spec</span><span class="token punctuation">:</span>
      <span class="token key atrule">restartPolicy</span><span class="token punctuation">:</span> OnFailure
      <span class="token key atrule">containers</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> echo
          <span class="token key atrule">image</span><span class="token punctuation">:</span> busybox
          <span class="token key atrule">command</span><span class="token punctuation">:</span>
            <span class="token punctuation">-</span> <span class="token string">"/bin/sh"</span>
          <span class="token key atrule">args</span><span class="token punctuation">:</span>
            <span class="token punctuation">-</span> <span class="token string">"-c"</span>
            <span class="token punctuation">-</span> <span class="token string">"for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>通过下面的命令创建job，可以通过<code>kubectl get pods -w</code>来观察job创建pod的过程和结果。最后可以通过<code>logs</code>命令查看日志。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl apply <span class="token parameter variable">-f</span> hello-job.yaml

kubectl get <span class="token function">jobs</span>                  
<span class="token comment"># NAME        COMPLETIONS   DURATION   AGE</span>
<span class="token comment"># hello-job   5/5           19s        83s</span>

kubectl get pods                      
<span class="token comment"># NAME                                   READY   STATUS      RESTARTS   AGE</span>
<span class="token comment"># hello-job--1-5gjjr                     0/1     Completed   0          34s</span>
<span class="token comment"># hello-job--1-8ffmn                     0/1     Completed   0          26s</span>
<span class="token comment"># hello-job--1-ltsvm                     0/1     Completed   0          34s</span>
<span class="token comment"># hello-job--1-mttwv                     0/1     Completed   0          29s</span>
<span class="token comment"># hello-job--1-ww2qp                     0/1     Completed   0          34s</span>

kubectl logs <span class="token parameter variable">-f</span> hello-job--1-5gjjr 
<span class="token comment"># 1</span>
<span class="token comment"># ...</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>Job完成时不会再创建新的Pod，不过已有的Pod<a target="_blank" rel="noopener" href="https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy">通常</a>也不会被删除。保留这些Pod使得你可以查看已完成的Pod的日志输出，以便检查错误、警告或者其它诊断性输出。可以使用<code>kubectl</code>来删除Job(例如<code>kubectl delete -f hello-job.yaml</code>)。当使用<code>kubectl</code>来删除Job时，该Job所创建的Pod也会被删除。</p>
<h2 id="CronJob"><a href="#CronJob" class="headerlink" title="CronJob"></a>CronJob</h2><p><em>CronJob</em>可以理解为定时任务，创建基于Cron时间调度的<a target="_blank" rel="noopener" href="https://kubernetes.io/docs/concepts/workloads/controllers/job/">Jobs</a>。</p>
<blockquote>
<p>CronJob用于执行周期性的动作，例如备份、报告生成等。这些任务中的每一个都应该配置为周期性重复的（例如：每天&#x2F;每周&#x2F;每月一次）；你可以定义任务开始执行的时间间隔。</p>
</blockquote>
<p>Cron时间表语法</p>
<pre class="line-numbers language-none"><code class="language-none"># ┌───────────── 分钟 (0 - 59)
# │ ┌───────────── 小时 (0 - 23)
# │ │ ┌───────────── 月的某天 (1 - 31)
# │ │ │ ┌───────────── 月份 (1 - 12)
# │ │ │ │ ┌───────────── 周的某天 (0 - 6)（周日到周一；在某些系统上，7 也是星期日）
# │ │ │ │ │                          或者是 sun，mon，tue，web，thu，fri，sat
# │ │ │ │ │
# │ │ │ │ │
# * * * * *<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>用法除了需要加上cron表达式之外，其余基本和Job保持一致。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> batch/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> CronJob
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hello<span class="token punctuation">-</span>cronjob
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">schedule</span><span class="token punctuation">:</span> <span class="token string">"* * * * *"</span> <span class="token comment"># Every minute</span>
  <span class="token key atrule">jobTemplate</span><span class="token punctuation">:</span>
    <span class="token key atrule">spec</span><span class="token punctuation">:</span>
      <span class="token key atrule">template</span><span class="token punctuation">:</span>
        <span class="token key atrule">spec</span><span class="token punctuation">:</span>
          <span class="token key atrule">restartPolicy</span><span class="token punctuation">:</span> OnFailure
          <span class="token key atrule">containers</span><span class="token punctuation">:</span>
            <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> echo
              <span class="token key atrule">image</span><span class="token punctuation">:</span> busybox
              <span class="token key atrule">command</span><span class="token punctuation">:</span>
                <span class="token punctuation">-</span> <span class="token string">"/bin/sh"</span>
              <span class="token key atrule">args</span><span class="token punctuation">:</span>
                <span class="token punctuation">-</span> <span class="token string">"-c"</span>
                <span class="token punctuation">-</span> <span class="token string">"for i in 9 8 7 6 5 4 3 2 1 ; do echo $i ; done"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>使用命令和Job也基本保持一致，这里就不过多赘述。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl apply <span class="token parameter variable">-f</span> hello-cronjob.yaml
<span class="token comment"># cronjob.batch/hello-cronjob created</span>

kubectl get cronjob                
<span class="token comment"># NAME            SCHEDULE    SUSPEND   ACTIVE   LAST SCHEDULE   AGE</span>
<span class="token comment"># hello-cronjob   * * * * *   False     0        &lt;none>          8s</span>

kubectl get pods   
<span class="token comment"># NAME                                   READY   STATUS      RESTARTS   AGE</span>
<span class="token comment"># hello-cronjob-27694609--1-2nmdx        0/1     Completed   0          15s</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h2 id="Helm"><a href="#Helm" class="headerlink" title="Helm"></a>Helm</h2><p>经过前面的教程，想必你已经对kubernetes的使用有了一定的理解。但是不知道你是否想过这样一个问题，就是我们前面教程中提到的所有资源，包括用<code>pod</code>,<code>deployment</code>,<code>service</code>,<code>ingress</code>,<code>configmap</code>,<code>secret</code>所有资源来部署一套完整的<code>hellok8s</code>服务的话，难道需要一个一个的<code>kubectl apply -f</code>来创建吗？如果换一个namespace，或者说换一套kubernetes集群部署的话，又要重复性的操作创建的过程吗？我们平常使用操作系统时，需要安装一个应用的话，可以直接使用<code>apt</code>或者<code>brew</code>来直接安装，而不需要关心这个应用需要哪些依赖，哪些配置。在使用kubernetes安装应用服务<code>hellok8s</code>时，我们自然也希望能够一个命令就安装完成，而提供这个能力的，就是CNCF的毕业项目<a target="_blank" rel="noopener" href="https://github.com/helm/helm">Helm</a>。</p>
<blockquote>
<p>Helm帮助您管理Kubernetes应用——Helm Chart，Helm是查找、分享和使用软件构建<a target="_blank" rel="noopener" href="https://kubernetes.io/">Kubernetes</a>的最优方式。</p>
<p>复杂性管理——即使是最复杂的应用，Helm Chart依然可以描述，提供使用单点授权的可重复安装应用程序。</p>
<p>易于升级——随时随地升级和自定义的钩子消除您升级的痛苦。</p>
<p>分发简单—— Helm Chart很容易在公共或私有化服务器上发版，分发和部署站点。</p>
<p>回滚——使用<code>helm rollback</code>可以轻松回滚到之前的发布版本。</p>
</blockquote>
<p>我们通过brew来安装helm。更多方式可以参考<a target="_blank" rel="noopener" href="https://helm.sh/zh/docs/intro/install/">官方文档</a>。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">brew <span class="token function">install</span> helm<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p>Helm的使用方式可以解释为：Helm安装<em>charts</em>到Kubernetes集群中，每次安装都会创建一个新的<em>release</em>。你可以在Helm的chart <em>repositories</em> 中寻找新的chart。</p>
<h3 id="用helm安装hellok8s"><a href="#用helm安装hellok8s" class="headerlink" title="用helm安装hellok8s"></a>用helm安装hellok8s</h3><p>开始本节教程前，我们先把之前手动创建的hellok8s相关的资源删除(防止使用helm创建同名的k8s资源失败)。在尝试自己创建hellok8s helm chart之前，我们可以先来熟悉一下怎么使用helm chart。在这里我先创建好了一个hellok8s（包括会创建deployment,service,ingress,configmaps,secret）的helm chart。通过GitHub actions生成放在了<a target="_blank" rel="noopener" href="https://github.com/guangzhengli/k8s-tutorials/tree/gh-pages/">gh-pages</a>分支下的<code>index.yaml</code>文件中。接着可以使用下面命令进行快速安装，其中<code>helm repo add</code>表示将我创建好的hellok8s chart添加到自己本地的仓库当中，<code>helm install</code>表示从仓库中安装hellok8s&#x2F;hello-helm到k8s集群当中。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">helm repo <span class="token function">add</span> hellok8s https://guangzhengli.github.io/k8s-tutorials/
<span class="token comment"># "hellok8s" has been added to your repositories</span>

helm <span class="token function">install</span> my-hello-helm hellok8s/hello-helm <span class="token parameter variable">--version</span> <span class="token number">0.1</span>.0
<span class="token comment"># NAME: my-hello-helm</span>
<span class="token comment"># NAMESPACE: default</span>
<span class="token comment"># STATUS: deployed</span>
<span class="token comment"># REVISION: 1</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>创建完成后，通过<code>kubectl get</code>等命令可以看到所有hellok8s资源都创建成功，<code>helm</code>一条命令即可做到之前教程中所有资源的创建！通过<code>curl</code>k8s集群的ingress地址，也可以看到返回字符串！</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl get pods
<span class="token comment"># NAME                                  READY   STATUS    RESTARTS   AGE</span>
<span class="token comment"># hellok8s-deployment-f88f984c6-k8hpz   1/1     Running   0          15h</span>
<span class="token comment"># hellok8s-deployment-f88f984c6-nzwg6   1/1     Running   0          15h</span>
<span class="token comment"># hellok8s-deployment-f88f984c6-s89s7   1/1     Running   0          15h</span>
<span class="token comment"># nginx-deployment-d47fd7f66-6w76b      1/1     Running   0          15h</span>
<span class="token comment"># nginx-deployment-d47fd7f66-tsqj5      1/1     Running   0          15h</span>

kubectl get deployments
<span class="token comment"># NAME                  READY   UP-TO-DATE   AVAILABLE   AGE</span>
<span class="token comment"># hellok8s-deployment   3/3     3            3           15h</span>
<span class="token comment"># nginx-deployment      2/2     2            2           15h</span>

kubectl get <span class="token function">service</span>
<span class="token comment"># NAME                         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE</span>
<span class="token comment"># kubernetes                   ClusterIP   10.96.0.1        &lt;none>        443/TCP    13d</span>
<span class="token comment"># service-hellok8s-clusterip   ClusterIP   10.107.198.175   &lt;none>        3000/TCP   15h</span>
<span class="token comment"># service-nginx-clusterip      ClusterIP   10.100.144.49    &lt;none>        4000/TCP   15h</span>

kubectl get ingress
<span class="token comment"># NAME               CLASS   HOSTS   ADDRESS     PORTS   AGE</span>
<span class="token comment"># hellok8s-ingress   nginx   *       localhost   80      15h</span>

kubectl get configmap
<span class="token comment"># NAME               DATA   AGE</span>
<span class="token comment"># hellok8s-config    1      15h</span>

kubectl get secret
<span class="token comment"># NAME                                  TYPE                                  DATA   AGE</span>
<span class="token comment"># hellok8s-secret                       Opaque                                1      15h</span>
<span class="token comment"># sh.helm.release.v1.my-hello-helm.v1   helm.sh/release.v1</span>

<span class="token function">curl</span> http://192.168.59.100/hello
<span class="token comment"># [v6] Hello, Helm! Message from helm values: It works with Helm Values[v2]!, From namespace: default, From host: hellok8s-deployment-598bbd6884-ttk78, Get Database Connect URL: http://DB_ADDRESS_DEFAULT, Database Connect Password: db_password</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h3 id="创建helm-charts"><a href="#创建helm-charts" class="headerlink" title="创建helm charts"></a>创建helm charts</h3><p>在使用已经创建好的hello-helm charts来创建整个hellok8s资源后，你可能还是有很多的疑惑，包括Chart是如何生成和发布的，如何创建一个新的Chart？在这节教程中，我们会尝试自己来创建hello-helm Chart来完成之前的操作。首先建议使用<code>helm create</code>命令来创建一个初始的Chart，该命令默认会创建一些k8s资源定义的初始文件，并且会生成官网推荐的目录结构，如下所示：</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">helm create hello-helm

<span class="token builtin class-name">.</span>
├── Chart.yaml
├── charts
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── hpa.yaml
│   ├── ingress.yaml
│   ├── service.yaml
│   ├── serviceaccount.yaml
│   └── tests
│       └── test-connection.yaml
└── values.yaml<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>我们将默认生成在templates目录下面的<code>yaml</code>文件删除，用之前教程中<code>yaml</code>文件替换它，最终的结构长这样：</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token builtin class-name">.</span>
├── Chart.yaml
├── Dockerfile
├── _helpers.tpl
├── charts
├── hello-helm-0.1.0.tgz
├── index.yaml
├── main.go
├── templates
│   ├── hellok8s-configmaps.yaml
│   ├── hellok8s-deployment.yaml
│   ├── hellok8s-secret.yaml
│   ├── hellok8s-service.yaml
│   ├── ingress.yaml
│   ├── nginx-deployment.yaml
│   └── nginx-service.yaml
└── values.yaml<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>其中<code>main.go</code>定义的是<code>hellok8s:v6</code>版本的代码，主要是从系统中拿到message，namespace，dbURL，dbPassword这几个环境变量，拼接成字符串返回。</p>
<pre class="line-numbers language-go" data-language="go"><code class="language-go"><span class="token keyword">package</span> main

<span class="token keyword">import</span> <span class="token punctuation">(</span>
	<span class="token string">"fmt"</span>
	<span class="token string">"io"</span>
	<span class="token string">"net/http"</span>
	<span class="token string">"os"</span>
<span class="token punctuation">)</span>

<span class="token keyword">func</span> <span class="token function">hello</span><span class="token punctuation">(</span>w http<span class="token punctuation">.</span>ResponseWriter<span class="token punctuation">,</span> r <span class="token operator">*</span>http<span class="token punctuation">.</span>Request<span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	host<span class="token punctuation">,</span> <span class="token boolean">_</span> <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Hostname</span><span class="token punctuation">(</span><span class="token punctuation">)</span>
	message <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Getenv</span><span class="token punctuation">(</span><span class="token string">"MESSAGE"</span><span class="token punctuation">)</span>
	namespace <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Getenv</span><span class="token punctuation">(</span><span class="token string">"NAMESPACE"</span><span class="token punctuation">)</span>
	dbURL <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Getenv</span><span class="token punctuation">(</span><span class="token string">"DB_URL"</span><span class="token punctuation">)</span>
	dbPassword <span class="token operator">:=</span> os<span class="token punctuation">.</span><span class="token function">Getenv</span><span class="token punctuation">(</span><span class="token string">"DB_PASSWORD"</span><span class="token punctuation">)</span>

	io<span class="token punctuation">.</span><span class="token function">WriteString</span><span class="token punctuation">(</span>w<span class="token punctuation">,</span> fmt<span class="token punctuation">.</span><span class="token function">Sprintf</span><span class="token punctuation">(</span><span class="token string">"[v6] Hello, Helm! Message from helm values: %s, From namespace: %s, From host: %s, Get Database Connect URL: %s, Database Connect Password: %s"</span><span class="token punctuation">,</span> message<span class="token punctuation">,</span> namespace<span class="token punctuation">,</span> host<span class="token punctuation">,</span> dbURL<span class="token punctuation">,</span> dbPassword<span class="token punctuation">)</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span>

<span class="token keyword">func</span> <span class="token function">main</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">&#123;</span>
	http<span class="token punctuation">.</span><span class="token function">HandleFunc</span><span class="token punctuation">(</span><span class="token string">"/"</span><span class="token punctuation">,</span> hello<span class="token punctuation">)</span>
	http<span class="token punctuation">.</span><span class="token function">ListenAndServe</span><span class="token punctuation">(</span><span class="token string">":3000"</span><span class="token punctuation">,</span> <span class="token boolean">nil</span><span class="token punctuation">)</span>
<span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>为了让大家更加了解helm charts values的使用和熟悉k8s资源配置，这几个环境变量<code>MESSAGE</code>,<code>NAMESPACE</code>,<code>DB_URL</code>,<code>DB_PASSWORD</code>分别有不同的来源。首先修改根目录下的<code>values.yaml</code>文件，定义自定义的配置信息，从之前教程的k8s资源文件中，将一些易于变化的参数提取出来，放在<code>values.yaml</code>文件中。全部配置信息如下所示：</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">application</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s
  <span class="token key atrule">hellok8s</span><span class="token punctuation">:</span>
    <span class="token key atrule">image</span><span class="token punctuation">:</span> guangzhengli/hellok8s<span class="token punctuation">:</span>v6
    <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">3</span>
    <span class="token key atrule">message</span><span class="token punctuation">:</span> <span class="token string">"It works with Helm Values!"</span>
    <span class="token key atrule">database</span><span class="token punctuation">:</span>
      <span class="token key atrule">url</span><span class="token punctuation">:</span> <span class="token string">"http://DB_ADDRESS_DEFAULT"</span>
      <span class="token key atrule">password</span><span class="token punctuation">:</span> <span class="token string">"db_password"</span>
  <span class="token key atrule">nginx</span><span class="token punctuation">:</span>
    <span class="token key atrule">image</span><span class="token punctuation">:</span> nginx
    <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token number">2</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>那自定义好了这些配置后，如何在k8s资源定义中使用这些配置信息呢？Helm默认使用<a target="_blank" rel="noopener" href="https://helm.sh/zh/docs/howto/charts_tips_and_tricks/">Go template的方式</a>来完成。例如之前教程中，将环境变量<code>DB_URL</code>定义在k8s configmaps中，那么该资源可以定义成如文件所示<code>hellok8s-configmaps.yaml</code>。其中<code>metadata.name</code>的值是<code>&#123;&#123; .Values.application.name &#125;&#125;-config</code>，意思是从<code>values.yaml</code>文件中获取<code>application.name</code>的值<code>hellok8s</code>，拼接<code>-config</code>字符串，这样创建出来的configmaps资源名称就是<code>hellok8s-config</code>。同理<code>&#123;&#123; .Values.application.hellok8s.database.url &#125;&#125;</code>就是获取值为<code>http://DB_ADDRESS_DEFAULT</code>放入configmaps对应key为DB_URL的value中。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> ConfigMap
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> .Values.application.name <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">-</span>config
<span class="token key atrule">data</span><span class="token punctuation">:</span>
  <span class="token key atrule">DB_URL</span><span class="token punctuation">:</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> .Values.application.hellok8s.database.url <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>上面定义的最终效果和之前在<code>configmaps</code>教程中定义的资源没有区别，这种做的好处是可以将所有可变的参数定义在<code>values.yaml</code>文件中，使用该helm charts的人无需了解具体k8s的定义，只需改变成自己想要的参数，即可创建自定义的资源！同样，我们可以根据之前的教程将<code>DB_PASSWORD</code>放入secret中，并且通过<code>b64enc</code>方法将值Base64编码。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash"><span class="token comment"># hellok8s-secret.yaml</span>
apiVersion: v1
kind: Secret
metadata:
  name: <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> .Values.application.name <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>-secret
data:
  DB_PASSWORD: <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> .Values.application.hellok8s.database.password <span class="token operator">|</span> b64enc <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>最后，修改<code>hellok8s-deployment</code>文件，根据前面的教程，将<code>metadata.name</code> <code>replicas</code> <code>image</code> <code>configMapKeyRef.name</code> <code>secretKeyRef.name</code>等值修改成从<code>values.yaml</code>文件中获取。再添加代码中需要的<code>NAMESPACE</code>环境变量，从<code>.Release.Namespace</code><a target="_blank" rel="noopener" href="https://helm.sh/zh/docs/chart_template_guide/builtin_objects/">内置对象</a>中获取。最后添加<code>MESSAGE</code>环境变量，直接从<code>&#123;&#123; .Values.application.hellok8s.message &#125;&#125;</code>中获取。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> .Values.application.name <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">-</span>deployment
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> .Values.application.hellok8s.replicas <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>
      <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
  <span class="token key atrule">template</span><span class="token punctuation">:</span>
    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>
      <span class="token key atrule">labels</span><span class="token punctuation">:</span>
        <span class="token key atrule">app</span><span class="token punctuation">:</span> hellok8s
    <span class="token key atrule">spec</span><span class="token punctuation">:</span>
      <span class="token key atrule">containers</span><span class="token punctuation">:</span>
        <span class="token punctuation">-</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> .Values.application.hellok8s.image <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>
          <span class="token key atrule">name</span><span class="token punctuation">:</span> hellok8s<span class="token punctuation">-</span>container
          <span class="token key atrule">env</span><span class="token punctuation">:</span>
            <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> DB_URL
              <span class="token key atrule">valueFrom</span><span class="token punctuation">:</span>
                <span class="token key atrule">configMapKeyRef</span><span class="token punctuation">:</span>
                  <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> .Values.application.name <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">-</span>config
                  <span class="token key atrule">key</span><span class="token punctuation">:</span> DB_URL
            <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> DB_PASSWORD
              <span class="token key atrule">valueFrom</span><span class="token punctuation">:</span>
                <span class="token key atrule">secretKeyRef</span><span class="token punctuation">:</span>
                  <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> .Values.application.name <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">-</span>secret
                  <span class="token key atrule">key</span><span class="token punctuation">:</span> DB_PASSWORD
            <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> NAMESPACE
              <span class="token key atrule">value</span><span class="token punctuation">:</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> .Release.Namespace <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>
            <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> MESSAGE
              <span class="token key atrule">value</span><span class="token punctuation">:</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> .Values.application.hellok8s.message <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>修改<code>ingress.yaml</code>将<code>metadata.name</code>的值，其它没有变化</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> networking.k8s.io/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Ingress
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> .Values.application.name <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span><span class="token punctuation">-</span>ingress
<span class="token punctuation">...</span>
<span class="token punctuation">...</span>
<span class="token punctuation">...</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><code>nginx-deployment.yaml</code></p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> apps/v1
<span class="token key atrule">kind</span><span class="token punctuation">:</span> Deployment
<span class="token key atrule">metadata</span><span class="token punctuation">:</span>
  <span class="token key atrule">name</span><span class="token punctuation">:</span> nginx<span class="token punctuation">-</span>deployment
<span class="token key atrule">spec</span><span class="token punctuation">:</span>
  <span class="token key atrule">replicas</span><span class="token punctuation">:</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> .Values.application.nginx.replicas <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>
  <span class="token key atrule">selector</span><span class="token punctuation">:</span>
    <span class="token key atrule">matchLabels</span><span class="token punctuation">:</span>
      <span class="token key atrule">app</span><span class="token punctuation">:</span> nginx
  <span class="token key atrule">template</span><span class="token punctuation">:</span>
    <span class="token key atrule">metadata</span><span class="token punctuation">:</span>
      <span class="token key atrule">labels</span><span class="token punctuation">:</span>
        <span class="token key atrule">app</span><span class="token punctuation">:</span> nginx
    <span class="token key atrule">spec</span><span class="token punctuation">:</span>
      <span class="token key atrule">containers</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> <span class="token punctuation">&#123;</span><span class="token punctuation">&#123;</span> .Values.application.nginx.image <span class="token punctuation">&#125;</span><span class="token punctuation">&#125;</span>
        <span class="token key atrule">name</span><span class="token punctuation">:</span> nginx<span class="token punctuation">-</span>container<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p><code>nginx-service.yaml</code>和<code>hellok8s-service.yaml</code>没有变化。所有代码可以在<a target="_blank" rel="noopener" href="https://github.com/guangzhengli/k8s-tutorials/tree/main/helm-charts/hello-helm">这里</a>查看。</p>
<p>稍微修改下默认生成的<code>Chart.yaml</code></p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">apiVersion</span><span class="token punctuation">:</span> v2
<span class="token key atrule">name</span><span class="token punctuation">:</span> hello<span class="token punctuation">-</span>helm
<span class="token key atrule">description</span><span class="token punctuation">:</span> A k8s tutorials in https<span class="token punctuation">:</span>//github.com/guangzhengli/k8s<span class="token punctuation">-</span>tutorials
<span class="token key atrule">type</span><span class="token punctuation">:</span> application
<span class="token key atrule">version</span><span class="token punctuation">:</span> 0.1.0
<span class="token key atrule">appVersion</span><span class="token punctuation">:</span> <span class="token string">"1.16.0"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>定义完成所有的helm资源后，首先<strong>将<code>hellok8s:v6</code>镜像打包推送到DockerHub</strong>。之后即可在<code>hello-helm</code>的目录下执行<code>helm upgrade</code>命令进行安装，安装成功后，执行curl命令便能直接得到结果！查看pod和service等资源，便会发现helm能一键安装所有资源！</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">helm upgrade <span class="token parameter variable">--install</span> hello-helm <span class="token parameter variable">--values</span> values.yaml <span class="token builtin class-name">.</span>
<span class="token comment"># Release "hello-helm" does not exist. Installing it now.</span>
<span class="token comment"># NAME: hello-helm</span>
<span class="token comment"># NAMESPACE: default</span>
<span class="token comment"># STATUS: deployed</span>
<span class="token comment"># REVISION: 1</span>

<span class="token function">curl</span> http://192.168.59.100/hello
<span class="token comment"># [v6] Hello, Helm! Message from helm values: It works with Helm Values!, From namespace: default, From host: hellok8s-deployment-57d7df7964-m6gcc, Get Database Connect URL: http://DB_ADDRESS_DEFAULT, Database Connect Password: db_password</span>

kubectl get pods
<span class="token comment"># NAME                                  READY   STATUS    RESTARTS   AGE</span>
<span class="token comment"># hellok8s-deployment-f88f984c6-k8hpz   1/1     Running   0          32m</span>
<span class="token comment"># hellok8s-deployment-f88f984c6-nzwg6   1/1     Running   0          32m</span>
<span class="token comment"># hellok8s-deployment-f88f984c6-s89s7   1/1     Running   0          32m</span>
<span class="token comment"># nginx-deployment-d47fd7f66-6w76b      1/1     Running   0          32m</span>
<span class="token comment"># nginx-deployment-d47fd7f66-tsqj5      1/1     Running   0          32m</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h4 id="rollback"><a href="#rollback" class="headerlink" title="rollback"></a>rollback</h4><p>Helm也提供了Rollback的功能，我们先修改一下<code>message: &quot;It works with Helm Values[v2]!&quot;</code>加上[v2]。</p>
<pre class="line-numbers language-none"><code class="language-none">application:
  name: hellok8s
  hellok8s:
    image: guangzhengli&#x2F;hellok8s:v6
    replicas: 3
    message: &quot;It works with Helm Values[v2]!&quot;
    database:
      url: &quot;http:&#x2F;&#x2F;DB_ADDRESS_DEFAULT&quot;
      password: &quot;db_password&quot;
  nginx:
    image: nginx
    replicas: 2<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>再执行<code>helm upgrade</code>命令更新k8s资源，通过<code>curl http://192.168.59.100/hello</code>可以看到资源已经更新。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">➜  hello-helm git:<span class="token punctuation">(</span>main<span class="token punctuation">)</span> ✗ helm upgrade <span class="token parameter variable">--install</span> hello-helm <span class="token parameter variable">--values</span> values.yaml <span class="token builtin class-name">.</span>
<span class="token comment"># Release "hello-helm" has been upgraded. Happy Helming!</span>
NAME: hello-helm
NAMESPACE: default
STATUS: deployed
REVISION: <span class="token number">2</span>

<span class="token function">curl</span> http://192.168.59.100/hello
<span class="token comment"># [v6] Hello, Helm! Message from helm values: It works with Helm Values[v2]!, From namespace: default, From host: hellok8s-deployment-598bbd6884-4b9bw, Get Database Connect URL: http://DB_ADDRESS_DEFAULT, Database Connect Password: db_password</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>如果这一次更新有问题的话，可以通过<code>helm rollback</code>快速回滚。通过下面命令看到，和deployment的rollback一样，回滚后REVISION版本都会增大到3而不是回滚到1，回滚后使用<code>curl</code>命令返回的v1版本的字符串。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">helm <span class="token function">ls</span>
<span class="token comment"># NAME            NAMESPACE       REVISION          STATUS          CHART                   APP VERSION</span>
<span class="token comment"># hello-helm      default         2                 deployed        hello-helm-0.1.0        1.16.0 </span>

helm rollback hello-helm <span class="token number">1</span>
<span class="token comment"># Rollback was a success! Happy Helming!</span>

helm <span class="token function">ls</span>
<span class="token comment"># NAME            NAMESPACE       REVISION          STATUS          CHART                   APP VERSION</span>
<span class="token comment"># hello-helm      default         3                 deployed        hello-helm-0.1.0        1.16.0 </span>

<span class="token function">curl</span> http://192.168.59.100/hello
<span class="token comment"># [v6] Hello, Helm! Message from helm values: It works with Helm Values!, From namespace: default, From host: hellok8s-deployment-57d7df7964-482xw, Get Database Connect URL: http://DB_ADDRESS_DEFAULT, Database Connect Password: db_password</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<h4 id="多环境配置"><a href="#多环境配置" class="headerlink" title="多环境配置"></a>多环境配置</h4><p>使用Helm也很容易多环境部署，新建<code>values-dev.yaml</code>文件，里面内容自定义<code>dev</code>环境需要的配置信息。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">application</span><span class="token punctuation">:</span>
  <span class="token key atrule">hellok8s</span><span class="token punctuation">:</span>
    <span class="token key atrule">message</span><span class="token punctuation">:</span> <span class="token string">"It works with Helm Values values-dev.yaml!"</span>
    <span class="token key atrule">database</span><span class="token punctuation">:</span>
      <span class="token key atrule">url</span><span class="token punctuation">:</span> <span class="token string">"http://DB_ADDRESS_DEV"</span>
      <span class="token key atrule">password</span><span class="token punctuation">:</span> <span class="token string">"db_password_dev"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>可以多次指定’–values -f’参数，最后（最右边）指定的文件优先级最高，这里最右边的是<code>values-dev.yaml</code>文件，所以<code>values-dev.yaml</code>文件中的值会覆盖<code>values.yaml</code>中相同的值，<code>-n dev</code>表示在名字为dev的namespace中创建k8s资源，执行完成后，我们可以通过<code>curl</code>命令看到返回的字符串中读取的是<code>values-dev.yaml</code>文件的配置，并且<code>From namespace = dev</code>。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">helm upgrade <span class="token parameter variable">--install</span> hello-helm <span class="token parameter variable">-f</span> values.yaml <span class="token parameter variable">-f</span> values-dev.yaml <span class="token parameter variable">-n</span> dev <span class="token builtin class-name">.</span>

<span class="token comment"># Release "hello-helm" does not exist. Installing it now.</span>
<span class="token comment"># NAME: hello-helm</span>
<span class="token comment"># NAMESPACE: dev</span>
<span class="token comment"># STATUS: deployed</span>
<span class="token comment"># REVISION: 1</span>

<span class="token function">curl</span> http://192.168.59.100/hello
<span class="token comment"># [v6] Hello, Helm! Message from helm values: It works with Helm Values values-dev.yaml!, From namespace: dev, From host: hellok8s-deployment-f5fff9df-89sn6, Get Database Connect URL: http://DB_ADDRESS_DEV, Database Connect Password: db_password_dev</span>

kubectl get pods <span class="token parameter variable">-n</span> dev
<span class="token comment"># NAME                                 READY   STATUS    RESTARTS   AGE</span>
<span class="token comment"># hellok8s-deployment-f5fff9df-89sn6   1/1     Running   0          4m23s</span>
<span class="token comment"># hellok8s-deployment-f5fff9df-tkh6g   1/1     Running   0          4m23s</span>
<span class="token comment"># hellok8s-deployment-f5fff9df-wmlpb   1/1     Running   0          4m23s</span>
<span class="token comment"># nginx-deployment-d47fd7f66-cdlmf     1/1     Running   0          4m23s</span>
<span class="token comment"># nginx-deployment-d47fd7f66-cgst2     1/1     Running   0          4m23s</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>除此之外，还可以使用’–set-file’设置独立的值，类似于<code>helm upgrade --install hello-helm -f values.yaml -f values-dev.yaml --set application.hellok8s.message=&quot;It works with set helm values&quot; -n dev .</code> 方式在命令中设置values的值，可以随意修改相关配置，此方法在CICD中经常用到。</p>
<h3 id="helm-chart打包和发布"><a href="#helm-chart打包和发布" class="headerlink" title="helm chart打包和发布"></a>helm chart打包和发布</h3><p>上面的例子展示了我们可以用一行命令在一个新的环境中安装所有需要的k8s资源！那么如何将helm chart打包、分发和下载呢？在官网中，提供了两种教程，一种是以<a target="_blank" rel="noopener" href="https://helm.sh/zh/docs/howto/chart_repository_sync_example/">GCS存储的教程</a>，还有一种是以<a target="_blank" rel="noopener" href="https://helm.sh/zh/docs/howto/chart_releaser_action/">GitHub Pages存储的教程</a>。这里我们使用第二种，并且使用<a target="_blank" rel="noopener" href="https://github.com/helm/chart-releaser-action">chart-releaser-action</a>来做自动发布，该action会默认生成helm chart发布到<code>gh-pages</code>分支上，本教程的hellok8s helm chart就发布在了本仓库的<a target="_blank" rel="noopener" href="https://github.com/guangzhengli/k8s-tutorials/tree/gh-pages/">gh-pages</a>分支上的<code>index.yaml</code>文件中。在使用action自动生成chart之前，我们可以先熟悉一下如何手动生成，在<code>hello-helm</code>目录下，执行<code>helm package</code>将chart目录打包到chart归档中。<code>helm repo index</code>命令可以基于包含打包chart的目录，生成仓库的索引文件<code>index.yaml</code>。最后，可以使用<code>helm upgrade --install *.tgz</code>命令将该指定包进行安装使用。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">helm package hello-helm
<span class="token comment"># Successfully packaged chart and saved it to: /Users/guangzheng.li/workspace/k8s-tutorials/hello-helm/hello-helm-0.1.0.tgz</span>

helm repo index <span class="token builtin class-name">.</span>

helm upgrade <span class="token parameter variable">--install</span> hello-helm hello-helm-0.1.0.tgz<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>基于上面的步骤，你可能已经想到，所谓的helm打包和发布，就是<code>hello-helm-0.1.0.tgz</code>文件和<code>index.yaml</code>生成和上传的一个过程。而helm下载和安装，就是如何将<code>.tgz</code>和<code>index.yaml</code>文件下载和<code>helm upgrade --install</code>的过程。接下来我们发布生成的hellok8s helm chart，先将手动生成的<code>hello-helm-0.1.0.tgz</code>和<code>index.yaml</code>文件删除，后续使用GitHub action自动生成和发布这两个文件。GitHub action的代码可以参考<a target="_blank" rel="noopener" href="https://helm.sh/zh/docs/howto/chart_releaser_action/">官网文档</a>或者本仓库<code>.github/workflows/release.yml</code>文件。代表当push代码到远程仓库时，将<code>helm-charts</code>目录下的所有charts自动打包和发布到<code>gh-pages</code>分支去(需要保证<code>gh-pages</code>分支已经存在，否则会报错)。</p>
<pre class="line-numbers language-yaml" data-language="yaml"><code class="language-yaml"><span class="token key atrule">name</span><span class="token punctuation">:</span> Release Charts

<span class="token key atrule">on</span><span class="token punctuation">:</span>
  <span class="token key atrule">push</span><span class="token punctuation">:</span>
    <span class="token key atrule">branches</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> main

<span class="token key atrule">jobs</span><span class="token punctuation">:</span>
  <span class="token key atrule">release</span><span class="token punctuation">:</span>
    <span class="token comment"># depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions</span>
    <span class="token comment"># see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token</span>
    <span class="token key atrule">permissions</span><span class="token punctuation">:</span>
      <span class="token key atrule">contents</span><span class="token punctuation">:</span> write
    <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest
    <span class="token key atrule">steps</span><span class="token punctuation">:</span>
      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Checkout
        <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v2
        <span class="token key atrule">with</span><span class="token punctuation">:</span>
          <span class="token key atrule">fetch-depth</span><span class="token punctuation">:</span> <span class="token number">0</span>

      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Configure Git
        <span class="token key atrule">run</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string">
          git config user.name "$GITHUB_ACTOR"
          git config user.email "$GITHUB_ACTOR@users.noreply.github.com"</span>

      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Install Helm
        <span class="token key atrule">uses</span><span class="token punctuation">:</span> azure/setup<span class="token punctuation">-</span>helm@v1
        <span class="token key atrule">with</span><span class="token punctuation">:</span>
          <span class="token key atrule">version</span><span class="token punctuation">:</span> v3.8.1

      <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Run chart<span class="token punctuation">-</span>releaser
        <span class="token key atrule">uses</span><span class="token punctuation">:</span> helm/chart<span class="token punctuation">-</span>releaser<span class="token punctuation">-</span>action@v1.4.0
        <span class="token key atrule">with</span><span class="token punctuation">:</span> 
          <span class="token key atrule">charts_dir</span><span class="token punctuation">:</span> helm<span class="token punctuation">-</span>charts
        <span class="token key atrule">env</span><span class="token punctuation">:</span>
          <span class="token key atrule">CR_TOKEN</span><span class="token punctuation">:</span> <span class="token string">"$&#123;&#123; secrets.GITHUB_TOKEN &#125;&#125;"</span><span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>

<p>接着配置仓库的 <code>Settings -&gt; Pages -&gt; Build and deployment -&gt; Branch</code>，选择 <code>gh-pages</code> 分支，GitHub 会自动在 <code>https://username.github.io/project</code> 发布 helm chart。最后，你可以将自己的helm charts发布到社区中去，可以考虑发布到<a target="_blank" rel="noopener" href="https://artifacthub.io/">ArtifactHub</a>中，像本仓库生成的helm charts即发布在<a target="_blank" rel="noopener" href="https://artifacthub.io/packages/helm/hellok8s/hello-helm">ArtifactHub hellok8s</a>中。</p>
<p><img src="https://cdn.jsdelivr.net/gh/guangzhengli/PicURL@master/uPic/tnvYFS.png" alt="tnvYFS"></p>
<h2 id="Dashboard"><a href="#Dashboard" class="headerlink" title="Dashboard"></a>Dashboard</h2><h3 id="kubernetes-dashboard"><a href="#kubernetes-dashboard" class="headerlink" title="kubernetes dashboard"></a>kubernetes dashboard</h3><blockquote>
<p>Dashboard是基于网页的Kubernetes用户界面。你可以使用Dashboard将容器应用部署到Kubernetes集群中，也可以对容器应用排错，还能管理集群资源。你可以使用Dashboard获取运行在集群中的应用的概览信息，也可以创建或者修改Kubernetes资源（如Deployment，Job，DaemonSet等等）。例如，你可以对Deployment实现弹性伸缩、发起滚动升级、重启Pod或者使用向导创建新的应用。</p>
</blockquote>
<p>在本地minikube环境，可以直接通过下面命令开启Dashboard。更多用法可以参考官网或者自行探索。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">minikube dashboard<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>

<p><img src="https://cdn.jsdelivr.net/gh/guangzhengli/PicURL@master/uPic/eB3MYd.png" alt="eB3MYd"></p>
<h3 id="K9s"><a href="#K9s" class="headerlink" title="K9s"></a>K9s</h3><p><a target="_blank" rel="noopener" href="https://k9scli.io/">K9s</a>是一个基于Terminal的轻量级UI，可以更加轻松的观察和管理已部署的k8s资源。使用方式非常简单，安装后输入<code>k9s</code>即可开启Terminal Dashboard，更多用法可以参考官网。</p>
<p><img src="https://cdn.jsdelivr.net/gh/guangzhengli/PicURL@master/uPic/83ybd4.png" alt="83ybd4"></p>
<h2 id="常用命令"><a href="#常用命令" class="headerlink" title="常用命令"></a>常用命令</h2><h3 id="一、基础核心命令-Pod-amp-节点"><a href="#一、基础核心命令-Pod-amp-节点" class="headerlink" title="一、基础核心命令 (Pod &amp; 节点)"></a>一、基础核心命令 (Pod &amp; 节点)</h3><p>这些命令用于查看和管理最基本的资源。</p>
<ol>
<li><strong>查看资源状态</strong></li>
</ol>
<p><code>kubectl get nodes</code> - 查看所有节点状态<br><code>kubectl get pods</code> - 查看默认命名空间下的Pod<br><code>kubectl get pods -A</code> 或 <code>kubectl get pods --all-namespaces</code> - 查看所有命名空间下的Pod<br><code>kubectl get pods -o wide</code> - 查看Pod的详细信息（包括所在节点和IP）<br><code>kubectl get all</code> - 查看当前命名空间下的所有资源（Pod, Service, Deployment等）</p>
<ol start="2">
<li><strong>查看资源详情</strong></li>
</ol>
<p><code>kubectl describe node &lt;node-name&gt;</code> - 查看某个节点的详细信息<br><code>kubectl describe pod &lt;pod-name&gt;</code> - 查看某个Pod的详细事件和配置</p>
<ol start="3">
<li><strong>删除资源</strong></li>
</ol>
<p><code>kubectl delete pod &lt;pod-name&gt;</code> - 删除一个Pod<br><code>kubectl delete deployment &lt;deployment-name&gt;</code> - 删除一个Deployment</p>
<ol start="4">
<li><strong>执行命令与日志</strong></li>
</ol>
<p><code>kubectl logs &lt;pod-name&gt;</code> - 查看Pod的日志<br><code>kubectl logs -f &lt;pod-name&gt;</code> - 实时流式查看Pod日志（类似<code>tail -f</code>）<br><code>kubectl logs &lt;pod-name&gt; -c &lt;container-name&gt;</code> - 查看Pod中指定容器的日志<br><code>kubectl exec -it &lt;pod-name&gt; -- /bin/bash</code> - 进入Pod的容器内部执行命令（交互式终端）</p>
<h3 id="二、部署与工作负载-Deployment-ReplicaSet"><a href="#二、部署与工作负载-Deployment-ReplicaSet" class="headerlink" title="二、部署与工作负载 (Deployment, ReplicaSet)"></a>二、部署与工作负载 (Deployment, ReplicaSet)</h3><p>这些命令用于管理应用的部署和扩展。</p>
<ol>
<li><strong>创建资源</strong></li>
</ol>
<p><code>kubectl create deployment &lt;deployment-name&gt; --image=&lt;image&gt;</code> - 创建一个 Deployment<br><code>kubectl apply -f &lt;file.yaml&gt;</code> - 通过YAML文件创建或更新资源（<strong>声明式命令，推荐</strong>）<br><code>kubectl create -f &lt;file.yaml&gt;</code> - 通过YAML文件创建资源（命令式命令）</p>
<ol start="2">
<li><strong>扩缩容</strong></li>
</ol>
<p><code>kubectl scale deployment/&lt;deployment-name&gt; --replicas=&lt;number&gt;</code> - 扩容或缩容Pod副本数量<br><code>kubectl autoscale deployment/&lt;deployment-name&gt; --min=2 --max=5 --cpu-percent=80</code> - 设置Pod水平自动扩缩</p>
<ol start="3">
<li><strong>更新与回滚</strong></li>
</ol>
<p><code>kubectl set image deployment/&lt;deployment-name&gt; &lt;container-name&gt;=&lt;new-image&gt;</code> - 更新Deployment的镜像，触发滚动更新<br><code>kubectl rollout status deployment/&lt;deployment-name&gt;</code> - 查看滚动更新状态<br><code>kubectl rollout history deployment/&lt;deployment-name&gt;</code> - 查看部署历史<br><code>kubectl rollout undo deployment/&lt;deployment-name&gt;</code> - 回滚到上一个版本<br><code>kubectl rollout undo deployment/&lt;deployment-name&gt; --to-revision=&lt;number&gt;</code> - 回滚到指定版本</p>
<hr>
<h3 id="三、服务与网络-Service-Ingress"><a href="#三、服务与网络-Service-Ingress" class="headerlink" title="三、服务与网络 (Service, Ingress)"></a>三、服务与网络 (Service, Ingress)</h3><p>这些命令用于管理网络访问。</p>
<ol>
<li><p><strong>查看服务</strong><br><code>kubectl get svc</code> 或 <code>kubectl get services</code> - 查看Service<br><code>kubectl get ingress</code> - 查看Ingress</p>
</li>
<li><p><strong>暴露服务</strong><br><code>kubectl expose deployment &lt;deployment-name&gt; --type=LoadBalancer --port=80 --target-port=8080</code> - 为Deployment创建一个Service并暴露出去</p>
</li>
<li><p><strong>端口转发</strong><br><code>kubectl port-forward &lt;pod-name&gt; &lt;local-port&gt;:&lt;pod-port&gt;</code> - 将本地端口转发到Pod的端口（用于本地调试）<br><code>kubectl port-forward svc/&lt;service-name&gt; &lt;local-port&gt;:&lt;service-port&gt;</code> - 将本地端口转发到Service</p>
</li>
</ol>
<hr>
<h3 id="四、配置与存储-ConfigMap-Secret-PVC"><a href="#四、配置与存储-ConfigMap-Secret-PVC" class="headerlink" title="四、配置与存储 (ConfigMap, Secret, PVC)"></a>四、配置与存储 (ConfigMap, Secret, PVC)</h3><p>这些命令用于管理应用配置和存储。</p>
<ol>
<li><p><strong>查看配置</strong><br><code>kubectl get configmap</code> - 查看ConfigMap<br><code>kubectl get secret</code> - 查看Secret<br><code>kubectl get pvc</code> - 查看持久卷声明</p>
</li>
<li><p><strong>从文件创建配置</strong><br><code>kubectl create configmap &lt;name&gt; --from-file=&lt;file-path&gt;</code> - 从文件创建ConfigMap<br><code>kubectl create secret generic &lt;name&gt; --from-file=&lt;file-path&gt;</code> - 从文件创建Secret</p>
</li>
</ol>
<hr>
<h3 id="五、命名空间-Namespace"><a href="#五、命名空间-Namespace" class="headerlink" title="五、命名空间 (Namespace)"></a>五、命名空间 (Namespace)</h3><p>用于资源隔离。</p>
<p><code>kubectl get ns</code> - 查看所有命名空间<br><code>kubectl create namespace &lt;namespace-name&gt;</code> - 创建一个命名空间<br><code>kubectl config set-context --current --namespace=&lt;namespace-name&gt;</code> - 切换当前上下文的默认命名空间（后续命令默认在该命名空间下执行）<br>在命令中指定命名空间：在任何<code>get</code>, <code>describe</code>等命令后加上<code>-n &lt;namespace-name&gt;</code></p>
<hr>
<h3 id="六、故障排查与诊断"><a href="#六、故障排查与诊断" class="headerlink" title="六、故障排查与诊断"></a>六、故障排查与诊断</h3><ol>
<li><p><strong>资源使用情况</strong><br><code>kubectl top nodes</code> - 查看节点的资源（CPU&#x2F;Memory）使用情况<br><code>kubectl top pods</code> - 查看Pod的资源使用情况（需要安装metrics-server）</p>
</li>
<li><p><strong>高级诊断</strong><br><code>kubectl api-resources</code> - 查看所有可用的API资源类型<br><code>kubectl explain &lt;resource&gt;</code> - 查看某个资源的YAML字段说明，写YAML时的神器</p>
</li>
</ol>
<hr>
<h3 id="七、上下文与配置-Context-amp-Config"><a href="#七、上下文与配置-Context-amp-Config" class="headerlink" title="七、上下文与配置 (Context &amp; Config)"></a>七、上下文与配置 (Context &amp; Config)</h3><p>用于管理多个Kubernetes集群。</p>
<p><code>kubectl config get-contexts</code> - 查看所有上下文（集群）<br><code>kubectl config use-context &lt;context-name&gt;</code> - 切换到指定上下文（集群）<br><code>kubectl cluster-info</code> - 显示集群信息</p>
<h3 id="使用技巧与最佳实践"><a href="#使用技巧与最佳实践" class="headerlink" title="使用技巧与最佳实践"></a>使用技巧与最佳实践</h3><ol>
<li><p><strong><code>-o</code> 输出格式</strong>：<code>get</code>命令配合<code>-o</code>参数非常强大。<br><code>kubectl get pods -o wide</code> （更宽的信息）<br><code>kubectl get pods -o yaml</code> （以YAML格式输出）<br><code>kubectl get pods -o json</code> （以JSON格式输出）<br><code>kubectl get pods -o name</code> （只输出资源名称）</p>
</li>
<li><p>**<code>--dry-run=client</code>**：试运行，不真正创建资源，用于验证命令或生成YAML模板。</p>
<pre class="line-numbers language-bash" data-language="bash"><code class="language-bash">kubectl create deployment my-app <span class="token parameter variable">--image</span><span class="token operator">=</span>nginx --dry-run<span class="token operator">=</span>client <span class="token parameter variable">-o</span> yaml <span class="token operator">></span> deployment.yaml<span aria-hidden="true" class="line-numbers-rows"><span></span></span></code></pre>
</li>
<li><p>** <code>-l</code>标签选择器**：根据标签过滤资源。<br><code>kubectl get pods -l app=nginx</code></p>
</li>
<li><p>**<code>-A</code> 代替 <code>--all-namespaces</code>**：更简洁。</p>
</li>
</ol>
<h2 id="文章"><a href="#文章" class="headerlink" title="文章"></a>文章</h2><table>
<thead>
<tr>
<th align="center"><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s/6smzsvYSbRvSPcpbfnH98A">带着问题学Kubernetes架构！</a></th>
<th align="center"><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s/B2tIs6YitA93iYxEZ_8Ovw">为什么大家都在学习k8s</a></th>
<th align="center"><a target="_blank" rel="noopener" href="https://kuboard.cn/">Kuboard-Kubernetes多集群管理界面</a></th>
</tr>
</thead>
<tbody><tr>
<td align="center"><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s/WWRp-e9QPcLg8-m-V3UU1Q">图文详解Kubernetes，傻瓜都能看懂！</a></td>
<td align="center"><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s/8UJnsx0NJyxlKXeduhg5Yw">Kubernetes缺少的多租户功能，你可以通过这些方式实现</a></td>
<td align="center"><a target="_blank" rel="noopener" href="https://k8s.iswbm.com/">https://k8s.iswbm.com/</a></td>
</tr>
<tr>
<td align="center"><a target="_blank" rel="noopener" href="https://k8s-tutorials.pages.dev/">https://k8s-tutorials.pages.dev/</a></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>

                
            </div>
            <hr/>

            

    <div class="reprint" id="reprint-statement">
        
            <div class="reprint__author">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-user">
                        文章作者:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="/blog/about" rel="external nofollow noreferrer">xmxe</a>
                </span>
            </div>
            <div class="reprint__type">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-link">
                        文章链接:
                    </i>
                </span>
                <span class="reprint-info">
                    <a href="https://xmxe.github.io/blog/posts/47c89cb0ed9b/">https://xmxe.github.io/blog/posts/47c89cb0ed9b/</a>
                </span>
            </div>
            <div class="reprint__notice">
                <span class="reprint-meta" style="font-weight: bold;">
                    <i class="fas fa-copyright">
                        版权声明:
                    </i>
                </span>
                <span class="reprint-info">
                    本博客所有文章除特別声明外，均采用
                    <a href="https://creativecommons.org/licenses/by/4.0/deed.zh" rel="external nofollow noreferrer" target="_blank">CC BY 4.0</a>
                    许可协议。转载请注明来源
                    <a href="/blog/about" target="_blank">xmxe</a>
                    !
                </span>
            </div>
        
    </div>

    <script async defer>
      document.addEventListener("copy", function (e) {
        let toastHTML = '<span>复制成功，请遵循本文的转载规则</span><button class="btn-flat toast-action" onclick="navToReprintStatement()" style="font-size: smaller">查看</a>';
        M.toast({html: toastHTML})
      });

      function navToReprintStatement() {
        $("html, body").animate({scrollTop: $("#reprint-statement").offset().top - 80}, 800);
      }
    </script>



            <div class="tag_share" style="display: block;">
                <div class="post-meta__tag-list" style="display: inline-block;">
                    
                        <div class="article-tag">
                            <span class="chip bg-color">无标签</span>
                        </div>
                    
                </div>
                <div class="post_share" style="zoom: 80%; width: fit-content; display: inline-block; float: right; margin: -0.15rem 0;">
                    <link rel="stylesheet" type="text/css" href="/blog/libs/share/css/share.min.css">
<div id="article-share">

    
    <div class="social-share" data-sites="qq,qzone,wechat,weibo,douban" data-wechat-qrcode-helper="<p>微信扫一扫即可分享！</p>"></div>
    <script src="/blog/libs/share/js/social-share.min.js"></script>
    

    

</div>

                </div>
            </div>
            
                <style>
    #reward {
        margin: 40px 0;
        text-align: center;
    }

    #reward .reward-link {
        font-size: 1.4rem;
        line-height: 38px;
    }

    #reward .btn-floating:hover {
        box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2), 0 5px 15px rgba(0, 0, 0, 0.2);
    }

    #rewardModal {
        width: 320px;
        height: 350px;
    }

    #rewardModal .reward-title {
        margin: 15px auto;
        padding-bottom: 5px;
    }

    #rewardModal .modal-content {
        padding: 10px;
    }

    #rewardModal .close {
        position: absolute;
        right: 15px;
        top: 15px;
        color: rgba(0, 0, 0, 0.5);
        font-size: 1.3rem;
        line-height: 20px;
        cursor: pointer;
    }

    #rewardModal .close:hover {
        color: #ef5350;
        transform: scale(1.3);
        -moz-transform:scale(1.3);
        -webkit-transform:scale(1.3);
        -o-transform:scale(1.3);
    }

    #rewardModal .reward-tabs {
        margin: 0 auto;
        width: 210px;
    }

    .reward-tabs .tabs {
        height: 38px;
        margin: 10px auto;
        padding-left: 0;
    }

    .reward-content ul {
        padding-left: 0 !important;
    }

    .reward-tabs .tabs .tab {
        height: 38px;
        line-height: 38px;
    }

    .reward-tabs .tab a {
        color: #fff;
        background-color: #ccc;
    }

    .reward-tabs .tab a:hover {
        background-color: #ccc;
        color: #fff;
    }

    .reward-tabs .wechat-tab .active {
        color: #fff !important;
        background-color: #22AB38 !important;
    }

    .reward-tabs .alipay-tab .active {
        color: #fff !important;
        background-color: #019FE8 !important;
    }

    .reward-tabs .reward-img {
        width: 210px;
        height: 210px;
    }
</style>

<div id="reward">
    <a href="#rewardModal" class="reward-link modal-trigger btn-floating btn-medium waves-effect waves-light red">赏</a>

    <!-- Modal Structure -->
    <div id="rewardModal" class="modal">
        <div class="modal-content">
            <a class="close modal-close"><i class="fas fa-times"></i></a>
            <h4 class="reward-title">你的赏识是我前进的动力</h4>
            <div class="reward-content">
                <div class="reward-tabs">
                    <ul class="tabs row">
                        <li class="tab col s6 alipay-tab waves-effect waves-light"><a href="#alipay">支付宝</a></li>
                        <li class="tab col s6 wechat-tab waves-effect waves-light"><a href="#wechat">微 信</a></li>
                    </ul>
                    <div id="alipay">
                        
                            <img src="/blog/medias/reward/alipay.jpg" class="reward-img" alt="支付宝打赏二维码">
                        
                    </div>
                    <div id="wechat">
                        
                            <img src="/blog/medias/reward/wechat.jpg" class="reward-img" alt="微信打赏二维码">
                        
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<script>
    $(function () {
        $('.tabs').tabs();
    });
</script>

            
        </div>
    </div>

    

    

    

    

    

    

    

    

<article id="prenext-posts" class="prev-next articles">
    <div class="row article-row">
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge left-badge text-color">
                <i class="fas fa-chevron-left"></i>&nbsp;上一篇</div>
            <div class="card">
                <a href="/blog/posts/8a25c83216a8/">
                    <div class="card-image">
                        
                        <img src="https://pic1.zhimg.com/70/v2-25803fd579557b184ec9282d63167976_1440w.avis" class="responsive-img" alt="Arthas教程">
                        
                        <span class="card-title">Arthas教程</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            常用命令stack输出当前方法被调用的调用路径。很多时候我们都知道一个方法被执行，但是有很多地方调用了它，你并不知道是谁调用了它，此时你需要的是stack命令。
stack com.baomidou.mybatisplus.extensio
                        

                    </div>
                    <div class="publish-info">
                        <span class="publish-date">
                            <!--
                            <i class="far fa-clock fa-fw icon-date"></i>2024-01-08
                            -->

                            
                                <i class="fas fa-user fa-fw"></i>
                                <a href="/blog/about" >
                                    xmxe
                                </a>
                            
                        </span>
                        <span class="publish-author">
                            
                                <i class="fas fa-bookmark fa-fw icon-category"></i>
                                
                                    <a href="/blog/categories/%E6%8A%80%E6%9C%AF%E6%A0%88/" class="post-category">
                                        技术栈
                                    </a>
                                
                            

                        </span>
                    </div>
                </div>
                
            </div>
        </div>
        
        
        <div class="article col s12 m6" data-aos="fade-up">
            <div class="article-badge right-badge text-color">
                下一篇&nbsp;<i class="fas fa-chevron-right"></i>
            </div>
            <div class="card">
                <a href="/blog/posts/4cd9fba2c490/">
                    <div class="card-image">
                        
                        <img src="https://picx.zhimg.com/v2-271693316513b5a0410ca80a2c160b5e_1440w.jpg" class="responsive-img" alt="JS数组操作">
                        
                        <span class="card-title">JS数组操作</span>
                    </div>
                </a>
                <div class="card-content article-content">
                    <div class="summary block-with-text">
                        
                            常用操作声明数组const fruits = new Array('Apple', 'Banana');
console.log(fruits.length);

// 通过数组字面量创建一个有2个元素的'fruits'数组.
const 
                        
                        
                    </div>
                    <div class="publish-info">
                        <span class="publish-date">
                            <!--
                            <i class="far fa-clock fa-fw icon-date"></i>2023-05-17
                            -->
                            
                                <i class="fas fa-user fa-fw"></i>
                                <a href="/blog/about" >
                                    xmxe
                                </a>
                            
                        </span>
                        <span class="publish-author">
                            
                                <i class="fas fa-bookmark fa-fw icon-category"></i>
                                
                                    <a href="/blog/categories/JS/" class="post-category">
                                        JS
                                    </a>
                                
                            
                                
                                <a href="/blog/tags/%E4%BB%A3%E7%A0%81%E5%AE%9E%E6%88%98/">
                                    <span class="chip bg-color">代码实战</span>
                                </a>
                                
                            
                            
                        </span>
                    </div>
                </div>
                
            </div>
        </div>
        
    </div>
</article>

</div>



<!-- 代码块功能依赖 -->
<script type="text/javascript" src="/blog/libs/codeBlock/codeBlockFuction.js"></script>

<!-- 代码语言 -->

<script type="text/javascript" src="/blog/libs/codeBlock/codeLang.js"></script>


<!-- 代码块复制 -->

<script type="text/javascript" src="/blog/libs/codeBlock/codeCopy.js"></script>


<!-- 代码块收缩 -->

<script type="text/javascript" src="/blog/libs/codeBlock/codeShrink.js"></script>


    </div>
    <div id="toc-aside" class="expanded col l3 hide-on-med-and-down">
        <div class="toc-widget card">
            <div class="toc-title"><i class="far fa-list-alt"></i>&nbsp;&nbsp;目录</div>
            <div id="toc-content"></div>
        </div>
    </div>
</div>

<!-- TOC 悬浮按钮. -->

<div id="floating-toc-btn" class="hide-on-med-and-down">
    <a class="btn-floating btn-large bg-color">
        <i class="fas fa-list-ul"></i>
    </a>
</div>


<script src="/blog/libs/tocbot/tocbot.min.js"></script>
<script>
    $(function () {
        tocbot.init({
            tocSelector: '#toc-content',
            contentSelector: '#articleContent',
            headingsOffset: -($(window).height() * 0.4 - 45),
            collapseDepth: Number('0'),
            headingSelector: 'h1, h2, h3, h4, h5, h6'
        });

        // modify the toc link href to support Chinese.
        let i = 0;
        let tocHeading = 'toc-heading-';
        $('#toc-content a').each(function () {
            $(this).attr('href', '#' + tocHeading + (++i));
        });

        // modify the heading title id to support Chinese.
        i = 0;
        $('#articleContent').children('h1, h2, h3, h4, h5, h6').each(function () {
            $(this).attr('id', tocHeading + (++i));
        });

        // Set scroll toc fixed.
        let tocHeight = parseInt($(window).height() * 0.4 - 64);
        let $tocWidget = $('.toc-widget');
        $(window).scroll(function () {
            let scroll = $(window).scrollTop();
            /* add post toc fixed. */
            if (scroll > tocHeight) {
                $tocWidget.addClass('toc-fixed');
            } else {
                $tocWidget.removeClass('toc-fixed');
            }
        });

        
        /* 修复文章卡片 div 的宽度. */
        let fixPostCardWidth = function (srcId, targetId) {
            let srcDiv = $('#' + srcId);
            if (srcDiv.length === 0) {
                return;
            }

            let w = srcDiv.width();
            if (w >= 450) {
                w = w + 21;
            } else if (w >= 350 && w < 450) {
                w = w + 18;
            } else if (w >= 300 && w < 350) {
                w = w + 16;
            } else {
                w = w + 14;
            }
            $('#' + targetId).width(w);
        };

        // 切换TOC目录展开收缩的相关操作.
        const expandedClass = 'expanded';
        let $tocAside = $('#toc-aside');
        let $mainContent = $('#main-content');
        $('#floating-toc-btn .btn-floating').click(function () {
            if ($tocAside.hasClass(expandedClass)) {
                $tocAside.removeClass(expandedClass).hide();
                $mainContent.removeClass('l9');
            } else {
                $tocAside.addClass(expandedClass).show();
                $mainContent.addClass('l9');
            }
            fixPostCardWidth('artDetail', 'prenext-posts');
        });
        
    });
</script>

    

</main>




    <footer class="page-footer bg-color">
    
        <link rel="stylesheet" href="/blog/libs/aplayer/APlayer.min.css">
<style>
    .aplayer .aplayer-lrc p {
        
        display: none;
        
        font-size: 12px;
        font-weight: 700;
        line-height: 16px !important;
    }

    .aplayer .aplayer-lrc p.aplayer-lrc-current {
        
        display: none;
        
        font-size: 15px;
        color: #42b983;
    }

    
    .aplayer.aplayer-fixed.aplayer-narrow .aplayer-body {
        left: -66px !important;
    }

    .aplayer.aplayer-fixed.aplayer-narrow .aplayer-body:hover {
        left: 0px !important;
    }

    
</style>
<div class="">
    
    <div class="row">
        <meting-js class="col l8 offset-l2 m10 offset-m1 s12"
                   server="netease"
                   type="song"
                   id="569200213"
                   fixed='true'
                   autoplay='false'
                   theme='#42b983'
                   loop='all'
                   order='random'
                   preload='auto'
                   volume='0.7'
                   list-folded='true'
        >
        </meting-js>
    </div>
</div>

<script src="/blog/libs/aplayer/APlayer.min.js"></script>
<!-- <script src="https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js"></script> -->

    
    <div class="container row center-align" style="margin-bottom: 0px !important;">
        <div class="col s12 m8 l8 copy-right">
            Copyright&nbsp;&copy;
            
            
                <span id="year">2022-2025
                </span>
            
            

            <a href="/blog/about" target="_blank">
                xmxe
            </a>
            |&nbsp;Powered by&nbsp;
            <a href="https://hexo.io/" target="_blank">Hexo</a>
            |&nbsp;Theme&nbsp;
            <a href="https://github.com/blinkfox/hexo-theme-matery" target="_blank">Matery</a>
            <br>

            
            
            
            
            
            

            
            <br>

            
            <br>

            
        </div>
        <div class="col s12 m4 l4 social-link2 ">
    <a href="https://github.com/xmxe" class="tooltipped" target="_blank" data-tooltip="GitHub" data-position="top" data-delay="50">
        <i class="fab fa-github"></i>
    </a>



    <a href="https://gitee.com/xmxe" class="tooltipped" target="_blank" data-tooltip="码云" data-position="top" data-delay="50">
        <svg width="19" height="19" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" style="position: relative; top: 2px; left: 0.5px;">
            <path d="M512 1024C230.4 1024 0 793.6 0 512S230.4 0 512 0s512 230.4 512 512-230.4 512-512 512z m259.2-569.6H480c-12.8 0-25.6 12.8-25.6 25.6v64c0 12.8 12.8 25.6 25.6 25.6h176c12.8 0 25.6 12.8 25.6 25.6v12.8c0 41.6-35.2 76.8-76.8 76.8h-240c-12.8 0-25.6-12.8-25.6-25.6V416c0-41.6 35.2-76.8 76.8-76.8h355.2c12.8 0 25.6-12.8 25.6-25.6v-64c0-12.8-12.8-25.6-25.6-25.6H416c-105.6 0-188.8 86.4-188.8 188.8V768c0 12.8 12.8 25.6 25.6 25.6h374.4c92.8 0 169.6-76.8 169.6-169.6v-144c0-12.8-12.8-25.6-25.6-25.6z" fill="#fff">
            </path>
        </svg>
    </a>

















    
        
          <a href="/blog/download" class="tooltipped" target="_blank" data-tooltip="下载" data-position="top" data-delay="50">
            <i class="fas fa-download"></i>
          </a>
        
    



    <style>
  .mobiledevice {
    display: none !important;
  }

  footer .wechat_qrcode {
    position: fixed;
  }

  /*微信二维码*/
  .wechat_qrcode {
    position: absolute;
    margin-left: 10px;
    bottom: 10px;
    background: url("/blog/medias/xcx.png");
    zoom:40%;
  }

  .wechat:hover .wechat_qrcode {
    width: 430px;
    height: 430px;
    animation: move 0.4s linear 1 normal;
  }

  @keyframes move {
    0% {
      transform: translate(100px, 0);
      opacity: 0;
    }
    50% {
      transform: translate(50px, 0);
      opacity: 0.5;
    }
    100% {
      transform: translate(0, 0);
      opacity: 1;
    }
  }

  @media only screen and (max-width: 601px) {
    .wechat {
      display: none !important;
    }
    .mobiledevice {
      display: inline-block !important;
    }
  }
</style>

<a href="javascript:;" class="wechat" data-position="top" data-delay="50">
  <i class="fab fa-weixin"></i>
  <img class="wechat_qrcode" />
</a>

<a
  href="javascript:;"
  class="tooltipped mobiledevice"
  data-tooltip="微信: 464817304"
  data-position="top"
  data-delay="50"
>
  <i class="fab fa-weixin"></i>
</a>

</div>
    </div>
</footer>

<div class="progress-bar"></div>


    <!-- 搜索遮罩框 -->
<div id="searchModal" class="modal">
    <div class="modal-content">
        <div class="search-header">
            <span class="title"><i class="fas fa-search"></i>&nbsp;&nbsp;搜索</span>
            <input type="search" id="searchInput" name="s" placeholder="请输入搜索的关键字"
                   class="search-input">
        </div>
        <div id="searchResult"></div>
    </div>
</div>

<script type="text/javascript">
$(function () {
    var searchFunc = function (path, search_id, content_id) {
        'use strict';
        $.ajax({
            url: path,
            dataType: "xml",
            success: function (xmlResponse) {
                // get the contents from search data
                var datas = $("entry", xmlResponse).map(function () {
                    return {
                        title: $("title", this).text(),
                        content: $("content", this).text(),
                        url: $("url", this).text()
                    };
                }).get();
                var $input = document.getElementById(search_id);
                var $resultContent = document.getElementById(content_id);
                $input.addEventListener('input', function () {
                    var str = '<ul class=\"search-result-list\">';
                    var keywords = this.value.trim().toLowerCase().split(/[\s\-]+/);
                    $resultContent.innerHTML = "";
                    if (this.value.trim().length <= 0) {
                        return;
                    }
                    // perform local searching
                    datas.forEach(function (data) {
                        var isMatch = true;
                        var data_title = data.title.trim().toLowerCase();
                        var data_content = data.content.trim().replace(/<[^>]+>/g, "").toLowerCase();
                        var data_url = data.url;
                        data_url = data_url.indexOf('/') === 0 ? data.url : '/' + data_url;
                        var index_title = -1;
                        var index_content = -1;
                        var first_occur = -1;
                        // only match artiles with not empty titles and contents
                        if (data_title !== '' && data_content !== '') {
                            keywords.forEach(function (keyword, i) {
                                index_title = data_title.indexOf(keyword);
                                index_content = data_content.indexOf(keyword);
                                if (index_title < 0 && index_content < 0) {
                                    isMatch = false;
                                } else {
                                    if (index_content < 0) {
                                        index_content = 0;
                                    }
                                    if (i === 0) {
                                        first_occur = index_content;
                                    }
                                }
                            });
                        }
                        // show search results
                        if (isMatch) {
                            str += "<li><a href='" + data_url + "' class='search-result-title'>" + data_title + "</a>";
                            var content = data.content.trim().replace(/<[^>]+>/g, "");
                            if (first_occur >= 0) {
                                // cut out 100 characters
                                var start = first_occur - 20;
                                var end = first_occur + 80;
                                if (start < 0) {
                                    start = 0;
                                }
                                if (start === 0) {
                                    end = 100;
                                }
                                if (end > content.length) {
                                    end = content.length;
                                }
                                var match_content = content.substr(start, end);
                                // highlight all keywords
                                keywords.forEach(function (keyword) {
                                    var regS = new RegExp(keyword, "gi");
                                    match_content = match_content.replace(regS, "<em class=\"search-keyword\">" + keyword + "</em>");
                                });

                                str += "<p class=\"search-result\">" + match_content + "...</p>"
                            }
                            str += "</li>";
                        }
                    });
                    str += "</ul>";
                    $resultContent.innerHTML = str;
                });
            }
        });
    };

    searchFunc('/blog/search.xml', 'searchInput', 'searchResult');
});
</script>

    <!-- 回到顶部按钮 -->
<div id="backTop" class="top-scroll">
    <a class="btn-floating btn-large waves-effect waves-light" href="#!">
        <i class="fas fa-arrow-up"></i>
    </a>
</div>

    <div class="stars-con">
  <div id="stars"></div>
  <div id="stars2"></div>
  <div id="stars3"></div>  
</div>

<!-- 白天和黑夜主题 -->



<script>
  function switchNightMode() {
    
      setTimeout(function () {
        $('body').hasClass('DarkMode') 
        ? ($('body').removeClass('DarkMode'), localStorage.setItem('isDark', '0'), $('#sum-moon-icon').removeClass("fa-sun").addClass('fa-moon')) 
        : ($('body').addClass('DarkMode'), localStorage.setItem('isDark', '1'), $('#sum-moon-icon').addClass("fa-sun").removeClass('fa-moon')),
          
        setTimeout(function () {
          $('.Cuteen_DarkSky').fadeOut(1e3, function () {
            $(this).remove()
          })
        }, 2e3)
      })
  }
</script>
    
    
    <script>
        /* 模式判断 */
        if (localStorage.getItem('isDark') === '1') {
            document.body.classList.add('DarkMode');
            $('#sum-moon-icon').addClass("fa-sun").removeClass('fa-moon')
        } else {
            document.body.classList.remove('DarkMode');
            $('#sum-moon-icon').removeClass("fa-sun").addClass('fa-moon')
        }
    </script>

    <script src="/blog/libs/materialize/materialize.min.js"></script>
    <script src="/blog/libs/masonry/masonry.pkgd.min.js"></script>
    <script src="/blog/libs/aos/aos.js"></script>
    <script src="/blog/libs/scrollprogress/scrollProgress.min.js"></script>
    <script src="/blog/libs/lightGallery/js/lightgallery-all.min.js"></script>
    <script src="/blog/js/matery.js"></script>

    <!-- Baidu Analytics -->

    <!-- Baidu Push -->

<script>
    (function () {
        var bp = document.createElement('script');
        var curProtocol = window.location.protocol.split(':')[0];
        if (curProtocol === 'https') {
            bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
        } else {
            bp.src = 'http://push.zhanzhang.baidu.com/push.js';
        }
        var s = document.getElementsByTagName("script")[0];
        s.parentNode.insertBefore(bp, s);
    })();
</script>

    
    

    

    

	
    

    

    
    <script type="text/javascript" src="/blog/libs/background/ribbon-dynamic.js" async="async"></script>
    

    

    <!-- 冒泡 -->
    
    <script type="text/javascript">
        // 只在桌面版网页启用特效
        // var windowWidth = $(window).width();
        
            document.write('<script type="text/javascript" src="/blog/libs/others/bubleAll.js"><\/script>');
        
        
    </script>
    

    <!-- 弹出文字 -->
    

</body>

</html>
